前言

本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo

在了解了Dubbo SPI后,我们来了解下Dubbo服务导出的过程。

Dubbo的配置是通过DubboNamespaceHandler读取解析的,其中会将Dubbo服务提供者封装成ServiceBean注入Spring容器中。而服务导出就是在ServiceBean的onApplicationEvent开始的。

想了解DubboNamespaceHandler的工作原理,请自行去了解Spring自定义标签,本文略。

前置工作

服务导出的入口方法是 ServiceBean 的 onApplicationEvent。因为代码过多,接下来会忽略部分代码,提供调用链条,专注于主要部分。

  1. // ServiceBean#onApplicationEvent(ContextRefreshedEvent event) ->
  2. // ServiceBean#export() ->
  3. // ServiceConfig#export() ->
  4. // ServiceConfig#doExport() ->
  5. // ServiceConfig#doExportUrls()
  6. private void doExportUrls() {
  7. // 加载注册中心链接
  8. List<URL> registryURLs = loadRegistries(true);
  9. // 遍历 protocols,并在每个协议下导出服务
  10. for (ProtocolConfig protocolConfig : protocols) {
  11. doExportUrlsFor1Protocol(protocolConfig, registryURLs);
  12. }
  13. }

Dubbo是支持多注册中心多协议的。下面继续看doExportUrlsFor1Protocol方法。

  1. private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
  2. ......
  3. // 判断协议名是否为 injvm(本地调用)
  4. if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
  5. protocolConfig.setRegister(false);
  6. map.put("notify", "false");
  7. }
  8. ......
  9. String scope = url.getParameter(Constants.SCOPE_KEY);
  10. // 如果 scope = none,则什么都不做
  11. if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
  12. // scope != remote,导出到本地
  13. if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
  14. exportLocal(url);
  15. }
  16. // scope != local,导出到远程
  17. if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
  18. if (logger.isInfoEnabled()) {
  19. logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
  20. }
  21. if (registryURLs != null && !registryURLs.isEmpty()) {
  22. for (URL registryURL : registryURLs) {
  23. ......
  24. // 为服务提供类(ref)生成 Invoker
  25. Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
  26. // DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
  27. DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
  28. // 导出服务,并生成 Exporter
  29. Exporter<?> exporter = protocol.export(wrapperInvoker);
  30. exporters.add(exporter);
  31. }
  32. } else {
  33. ......
  34. }
  35. }
  36. }
  37. this.urls.add(url);
  38. }

这里我们只保留主要代码,完整的注释可以去看我fork的注释版源码或者官方开发指南。

这里主要逻辑有3部分:

  1. 本地服务暴露

    exportLocal(url);

  2. invoker的生成

    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));

  3. 导出服务,并生成 Exporter

    Exporter<?> exporter = protocol.export(wrapperInvoker);

    导出服务包含了远程服务暴露和注册中心处理。

下面我们来一一讲解这3部分

Invoker创建过程

在讲服务暴露之前,需要先了解下Invoker

官方文档是这么说的

Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。

Invoker 是由 ProxyFactory 创建而来,ProxyFactory 是方法级别的自适应拓展接口,其生成的自适应拓展类如下:

  1. package com.alibaba.dubbo.rpc;
  2. import com.alibaba.dubbo.common.extension.ExtensionLoader;
  3. public class ProxyFactory$Adaptive implements com.alibaba.dubbo.rpc.ProxyFactory {
  4. public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
  5. if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
  6. if (arg0.getUrl() == null)
  7. throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
  8. com.alibaba.dubbo.common.URL url = arg0.getUrl();
  9. String extName = url.getParameter("proxy", "javassist");
  10. if (extName == null)
  11. throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
  12. com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
  13. return extension.getProxy(arg0);
  14. }
  15. public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0, boolean arg1) throws com.alibaba.dubbo.rpc.RpcException {
  16. if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
  17. if (arg0.getUrl() == null)
  18. throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
  19. com.alibaba.dubbo.common.URL url = arg0.getUrl();
  20. String extName = url.getParameter("proxy", "javassist");
  21. if (extName == null)
  22. throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
  23. com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
  24. return extension.getProxy(arg0, arg1);
  25. }
  26. public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws com.alibaba.dubbo.rpc.RpcException {
  27. if (arg2 == null) throw new IllegalArgumentException("url == null");
  28. com.alibaba.dubbo.common.URL url = arg2;
  29. String extName = url.getParameter("proxy", "javassist");
  30. if (extName == null)
  31. throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
  32. com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
  33. return extension.getInvoker(arg0, arg1, arg2);
  34. }
  35. }

从上面可以看到,默认的实现类是JavassistProxyFactory,进去看看是如何创建invoker的

  1. public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
  2. // 为目标类创建 Wrapper
  3. // 此处是动态生成的Wrapper的实现类,会重写invokeMethod方法
  4. final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
  5. // 创建匿名 Invoker 类对象,并实现 doInvoke 方法。
  6. return new AbstractProxyInvoker<T>(proxy, type, url) {
  7. @Override
  8. protected Object doInvoke(T proxy, String methodName,
  9. Class<?>[] parameterTypes,
  10. Object[] arguments) throws Throwable {
  11. return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
  12. }
  13. };
  14. }

这里创建的是抽象类AbstractProxyInvoker,使用的是经典的模板模式,具体的逻辑由子类实现,去看看 AbstractProxyInvoker 是怎么封装 invoke 方法的

  1. public Result invoke(Invocation invocation) throws RpcException {
  2. try {
  3. // 调用 doInvoke 执行后续的调用,并将调用结果封装到 RpcResult 中
  4. return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));
  5. } catch (InvocationTargetException e) {
  6. return new RpcResult(e.getTargetException());
  7. } catch (Throwable e) {
  8. throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);
  9. }
  10. }

别问我wrapper是怎么生成的,我也看不懂。。。

本地服务暴露

当一个应用既是一个服务的提供者,同时也是这个服务的消费者的时候,可以直接对本机提供的服务发起本地调用。一般情况下也用不到,所以不感兴趣的可以略过此节。

当不指定范围为远程时,Dubbo默认支持本地调用的,参见前面的doExportUrlsFor1Protocol方法

  1. private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
  2. ......
  3. String scope = url.getParameter(Constants.SCOPE_KEY);
  4. // 如果 scope = none,则什么都不做
  5. if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
  6. // scope != remote,导出到本地
  7. if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
  8. exportLocal(url);
  9. }
  10. .....
  11. }
  12. }
  13. private void exportLocal(URL url) {
  14. // 如果 URL 的协议头等于 injvm,说明已经导出到本地了,无需再次导出
  15. if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
  16. // 将协议设置为injvm
  17. URL local = URL.valueOf(url.toFullString())
  18. .setProtocol(Constants.LOCAL_PROTOCOL)
  19. .setHost(LOCALHOST)
  20. .setPort(0);
  21. StaticContext.getContext(Constants.SERVICE_IMPL_CLASS).put(url.getServiceKey(), getServiceClass(ref));
  22. // 创建 Invoker,并导出服务,这里的 protocol 会在运行时调用 InjvmProtocol 的 export 方法
  23. Exporter<?> exporter = protocol.export(
  24. proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
  25. exporters.add(exporter);
  26. logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
  27. }
  28. }

重点是protocol.export(proxyFactory.getInvoker(ref, (Class) interfaceClass, local));这句,前面已经讲了invoker的生成,略过proxyFactory.getInvoker,直接看protocol.export,因为protocol也是方法级别的自适应拓展,下面照旧放上自适应拓展生成的类

  1. package com.alibaba.dubbo.rpc;
  2. import com.alibaba.dubbo.common.extension.ExtensionLoader;
  3. public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
  4. public void destroy() {
  5. throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
  6. }
  7. public int getDefaultPort() {
  8. throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
  9. }
  10. public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
  11. if (arg1 == null) throw new IllegalArgumentException("url == null");
  12. com.alibaba.dubbo.common.URL url = arg1;
  13. String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
  14. if (extName == null)
  15. throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
  16. com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
  17. return extension.refer(arg0, arg1);
  18. }
  19. public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
  20. if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
  21. if (arg0.getUrl() == null)
  22. throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
  23. com.alibaba.dubbo.common.URL url = arg0.getUrl();
  24. String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
  25. if (extName == null)
  26. throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
  27. com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
  28. return extension.export(arg0);
  29. }
  30. }

默认协议使用的是dubbo,但是 exportLocal 方法中,已经将协议转成LOCAL_PROTOCOL,也就是InjvmProtocol,下面去看看他的 export 方法做了什么

  1. public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
  2. return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
  3. }

这里只创建了一个 InjvmExporter,无其他逻辑。具体使用结合服务的引出和调用过程才能分析清除,此处不多赘述。

远程服务暴露

让我们回到 前置准备 小节的末尾,前面说了 protocol.export(wrapperInvoker) 方法包含了远程服务暴露和注册中心处理。参数 wrapperInvoker 中的url的协议是 registry ,所以实际调用的是RegistryProtocol的export方法

  1. public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
  2. // 远程服务暴露
  3. final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
  4. // 获取注册中心 URL,以 zookeeper 注册中心为例,得到的示例 URL 如下:
  5. // zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F172.17.48.52%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider
  6. URL registryUrl = getRegistryUrl(originInvoker);
  7. // 根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry,这里已经连接上注册中心,但是还没有将服务注册上去
  8. final Registry registry = getRegistry(originInvoker);
  9. // 获取已注册的服务提供者 URL,比如:
  10. // dubbo://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello
  11. final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);
  12. // 获取 register 参数
  13. boolean register = registeredProviderUrl.getParameter("register", true);
  14. // 向服务提供者与消费者注册表中注册服务提供者
  15. ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);
  16. // 根据 register 的值决定是否注册服务
  17. if (register) {
  18. // 向注册中心注册服务
  19. register(registryUrl, registeredProviderUrl);
  20. ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
  21. }
  22. ......
  23. return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
  24. }

这里有3个重点:

  1. 远程服务暴露

    doLocalExport(originInvoker);

  2. 创建注册中心连接

    getRegistry(originInvoker)

  3. 向注册中心注册服务

    register(registryUrl, registeredProviderUrl)

本节重点在远程服务暴露,所以继续看doLocalExport方法

  1. private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
  2. String key = getCacheKey(originInvoker);
  3. // 访问缓存
  4. ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
  5. if (exporter == null) {
  6. synchronized (bounds) {
  7. exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
  8. if (exporter == null) {
  9. // 创建 Invoker 为委托类对象
  10. final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
  11. // 调用 protocol 的 export 方法导出服务
  12. exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
  13. bounds.put(key, exporter);
  14. }
  15. }
  16. }
  17. return exporter;
  18. }

这里就是一些缓存操作,ExporterChangeableWrapper就是对unexport方法做了一下包装,逻辑很简单。我们重点关注protocol.export(invokerDelegete),而protocol是自适应拓展类,根据参数invokerDelegete中的url,实际调用的是DubboProtocol#export(invokerDelegete)

  1. public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
  2. URL url = invoker.getUrl();
  3. // 获取服务标识,理解成服务坐标也行。由服务组名,服务名,服务版本号以及端口组成。比如:
  4. // demoGroup/com.alibaba.dubbo.demo.DemoService:1.0.1:20880
  5. String key = serviceKey(url);
  6. // 创建 DubboExporter
  7. DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
  8. // 将 <key, exporter> 键值对放入缓存中
  9. exporterMap.put(key, exporter);
  10. // 本地存根相关代码
  11. ......
  12. // 启动服务器
  13. openServer(url);
  14. // 优化序列化
  15. optimizeSerialization(url);
  16. return exporter;
  17. }

Dubbo的网络传输层默认使用的是Netty,openServer方法就是启动netty服务,进去看看

  1. private void openServer(URL url) {
  2. // 获取 host:port,并将其作为服务器实例的 key,用于标识当前的服务器实例
  3. String key = url.getAddress();
  4. boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
  5. if (isServer) {
  6. ExchangeServer server = serverMap.get(key);
  7. if (server == null) {
  8. // 创建服务器实例
  9. serverMap.put(key, createServer(url));
  10. } else {
  11. // 服务器已创建,则根据 url 中的配置重置服务器
  12. server.reset(url);
  13. }
  14. }
  15. }

根据ip+端口判断是否已经创建,已经创建就根据 url 中的配置重置服务器(例如修改线程池配置,这个涉及线程派发模型,此处不多赘述)。我们重点关注服务器实例的创建。

  1. private ExchangeServer createServer(URL url) {
  2. url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
  3. // 添加心跳检测配置到 url 中
  4. url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
  5. // 获取 server 参数,默认为 netty
  6. String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
  7. // 通过 SPI 检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常
  8. if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
  9. throw new RpcException("Unsupported server type: " + str + ", url: " + url);
  10. // 添加编码解码器参数
  11. url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
  12. ExchangeServer server;
  13. try {
  14. // 创建 ExchangeServer
  15. server = Exchangers.bind(url, requestHandler);
  16. } catch (RemotingException e) {
  17. throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
  18. }
  19. // 获取 client 参数,可指定 netty,mina
  20. str = url.getParameter(Constants.CLIENT_KEY);
  21. if (str != null && str.length() > 0) {
  22. // 获取所有的 Transporter 实现类名称集合,比如 supportedTypes = [netty, mina]
  23. Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
  24. // 检测当前 Dubbo 所支持的 Transporter 实现类名称列表中,
  25. // 是否包含 client 所表示的 Transporter,若不包含,则抛出异常
  26. if (!supportedTypes.contains(str)) {
  27. throw new RpcException("Unsupported client type: " + str);
  28. }
  29. }
  30. return server;
  31. }

这里我们关注Exchangers.bind(url, requestHandler)方法,其前面逻辑是检测是否支持server所需的协议,后面的逻辑是检测是否支持client所需的协议。

  1. public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
  2. ......
  3. url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
  4. // 获取 Exchanger,默认为 HeaderExchanger。
  5. // 紧接着调用 HeaderExchanger 的 bind 方法创建 ExchangeServer 实例
  6. return getExchanger(url).bind(url, handler);
  7. }

不多bb,直接跳到HeaderExchanger.bind(url, handler)

  1. public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
  2. // 创建 HeaderExchangeServer 实例,该方法包含了多个逻辑,分别如下:
  3. // 1. new HeaderExchangeHandler(handler)
  4. // 2. new DecodeHandler(new HeaderExchangeHandler(handler))
  5. // 3. Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))
  6. return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
  7. }

HeaderExchangeHandler 和 DecodeHandler 都是handler的装饰类。HeaderExchangeServer内部持有Server,并封装了心跳的功能。不多赘述,我们的重点在 Transporters.bind ,也就是如何生成Server

  1. public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
  2. ......
  3. ChannelHandler handler;
  4. if (handlers.length == 1) {
  5. handler = handlers[0];
  6. } else {
  7. // 如果 handlers 元素数量大于1,则创建 ChannelHandler 分发器
  8. handler = new ChannelHandlerDispatcher(handlers);
  9. }
  10. // 获取自适应 Transporter 实例,并调用实例方法
  11. return getTransporter().bind(url, handler);
  12. }
  13. public static Transporter getTransporter() {
  14. return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension();
  15. }

getTransporter() 获取的是自适应拓展类,默认是NettyTransporter, 去看看

  1. public Server bind(URL url, ChannelHandler listener) throws RemotingException {
  2. return new NettyServer(url, listener);
  3. }

这里就创建了一个NettyServer对象,很明显,启动服务的相关逻辑在NettyServer的构造函数中

  1. public class NettyServer extends AbstractServer implements Server {
  2. // 构造方法
  3. public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
  4. super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
  5. }
  6. }
  7. public abstract class AbstractServer extends AbstractEndpoint implements Server {
  8. public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
  9. ......
  10. try {
  11. // 调用模板方法 doOpen 启动服务器
  12. doOpen();
  13. if (logger.isInfoEnabled()) {
  14. logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
  15. }
  16. } catch (Throwable t) {
  17. throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()
  18. + " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);
  19. }
  20. ......
  21. }
  22. }

NettyServer 调用的是父类的构造,而在父类 AbstractServer 中,又调用了子类的 doOpen() ,明显的模板模式。重新回到 NettyServer 看 doOpen 方法

  1. protected void doOpen() throws Throwable {
  2. bootstrap = new ServerBootstrap();
  3. // 创建 boss 和 worker 线程池
  4. bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServerBoss", true));
  5. workerGroup = new NioEventLoopGroup(getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
  6. new DefaultThreadFactory("NettyServerWorker", true));
  7. final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
  8. channels = nettyServerHandler.getChannels();
  9. bootstrap.group(bossGroup, workerGroup)
  10. .channel(NioServerSocketChannel.class)
  11. .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
  12. .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
  13. .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
  14. .childHandler(new ChannelInitializer<NioSocketChannel>() {
  15. @Override
  16. protected void initChannel(NioSocketChannel ch) throws Exception {
  17. NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
  18. ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
  19. .addLast("decoder", adapter.getDecoder())
  20. .addLast("encoder", adapter.getEncoder())
  21. .addLast("handler", nettyServerHandler);
  22. }
  23. });
  24. // 绑定到指定的 ip 和端口上
  25. ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
  26. channelFuture.syncUninterruptibly();
  27. channel = channelFuture.channel();
  28. }

这段是Netty启动服务的代码,就不多解释了。

至此,远程服务暴露的过程就分析完了。

注册中心处理

OK,让我们把视线放回RegistryProtocolexport方法中,上节说了远程服务暴露,本节就来说说剩下的创建注册中心连接以及向注册中心注册服务

创建注册中心连接

创建注册中心连接,是在getRegistry(originInvoker)方法中

  1. private Registry getRegistry(final Invoker<?> originInvoker) {
  2. URL registryUrl = getRegistryUrl(originInvoker);
  3. return registryFactory.getRegistry(registryUrl);
  4. }

registryFactory 是自适应拓展类,根据参数 registryUrl 的协议protocol字段,可知实际调用的是ZookeeperRegistryFactory,getRegistry 方法在ZookeeperRegistryFactory的父类AbstractRegistryFactory

  1. // AbstractRegistryFactory#getRegistry(URL url)
  2. public Registry getRegistry(URL url) {
  3. url = url.setPath(RegistryService.class.getName())
  4. .addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
  5. .removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
  6. String key = url.toServiceStringWithoutResolving();
  7. LOCK.lock();
  8. try {
  9. Registry registry = REGISTRIES.get(key);
  10. if (registry != null) {
  11. return registry;
  12. }
  13. // 缓存未命中,创建 Registry 实例
  14. registry = createRegistry(url);
  15. if (registry == null) {
  16. throw new IllegalStateException("Can not create registry " + url);
  17. }
  18. REGISTRIES.put(key, registry);
  19. return registry;
  20. } finally {
  21. // Release the lock
  22. LOCK.unlock();
  23. }
  24. }

这里就是一些URL的参数处理以及缓存操作,主要看createRegistry(url),此方法由子类ZookeeperRegistryFactory实现

  1. public class ZookeeperRegistryFactory extends AbstractRegistryFactory {
  2. private ZookeeperTransporter zookeeperTransporter;
  3. // zookeeperTransporter 由 SPI 在运行时注入,类型为 ZookeeperTransporter$Adaptive
  4. public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
  5. this.zookeeperTransporter = zookeeperTransporter;
  6. }
  7. @Override
  8. public Registry createRegistry(URL url) {
  9. return new ZookeeperRegistry(url, zookeeperTransporter);
  10. }
  11. }

这里注意一下,zookeeperTransporter 是由Dubbo SPI机制自动注入的。createRegistry 方法就创建了 ZookeeperRegistry 对象,所以处理逻辑应该就在它的构造方法中。

  1. public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
  2. ......
  3. // 创建 Zookeeper 客户端,默认为 CuratorZookeeperTransporte
  4. zkClient = zookeeperTransporter.connect(url);
  5. // 添加状态监听器
  6. zkClient.addStateListener(new StateListener() {
  7. @Override
  8. public void stateChanged(int state) {
  9. if (state == RECONNECTED) {
  10. try {
  11. recover();
  12. } catch (Exception e) {
  13. logger.error(e.getMessage(), e);
  14. }
  15. }
  16. }
  17. });
  18. }

这里,我们关注的是 zookeeperTransporter.connect(url),zookeeperTransporter的默认实现类是CuratorZookeeperTransporte,它的 connect 方法就是创建了CuratorZookeeperClient,所以直接去看构造方法

  1. public CuratorZookeeperClient(URL url) {
  2. super(url);
  3. try {
  4. int timeout = url.getParameter(Constants.TIMEOUT_KEY, 5000);
  5. // 创建 CuratorFramework 构造器
  6. CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
  7. .connectString(url.getBackupAddress())
  8. .retryPolicy(new RetryNTimes(1, 1000))
  9. .connectionTimeoutMs(timeout);
  10. String authority = url.getAuthority();
  11. if (authority != null && authority.length() > 0) {
  12. builder = builder.authorization("digest", authority.getBytes());
  13. }
  14. // 构建 CuratorFramework 实例
  15. client = builder.build();
  16. // 添加监听器
  17. client.getConnectionStateListenable().addListener(new ConnectionStateListener() {
  18. @Override
  19. public void stateChanged(CuratorFramework client, ConnectionState state) {
  20. if (state == ConnectionState.LOST) {
  21. CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED);
  22. } else if (state == ConnectionState.CONNECTED) {
  23. CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED);
  24. } else if (state == ConnectionState.RECONNECTED) {
  25. CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED);
  26. }
  27. }
  28. });
  29. // 启动客户端
  30. client.start();
  31. } catch (Exception e) {
  32. throw new IllegalStateException(e.getMessage(), e);
  33. }
  34. }

这里就是 Apache Curator 操作 zookeeper 的一些相关操作了,不多赘述。至此,注册中心创建过程结束,已连接 zookeeper

向注册中心注册服务

注册中心已创建,下一步就是向注册中心注册服务。

回到RegistryProtocolexport方法中,register(registryUrl, registeredProviderUrl) 方法就是向注册中心注册服务

  1. public void register(URL registryUrl, URL registedProviderUrl) {
  2. // 创建注册中心(此时会从缓存获取)
  3. Registry registry = registryFactory.getRegistry(registryUrl);
  4. // 将服务注册到注册中心
  5. registry.register(registedProviderUrl);
  6. }

registryFactory.getRegistry(registryUrl) 在上一节讲过,已经创建了注册中心(类型为ZookeeperRegistry),此时会命中缓存,直接返回。

ZookeeperRegistry并没有实现 register 方法,在其父类FailbackRegistry中。

  1. public void register(URL url) {
  2. super.register(url);
  3. failedRegistered.remove(url);
  4. failedUnregistered.remove(url);
  5. try {
  6. // 模板方法,由子类实现
  7. doRegister(url);
  8. } catch (Exception e) {
  9. Throwable t = e;
  10. // 获取 check 参数,若 check = true 将会直接抛出异常
  11. boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
  12. && url.getParameter(Constants.CHECK_KEY, true)
  13. && !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
  14. boolean skipFailback = t instanceof SkipFailbackWrapperException;
  15. if (check || skipFailback) {
  16. if (skipFailback) {
  17. t = t.getCause();
  18. }
  19. throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
  20. } else {
  21. logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
  22. }
  23. // 记录注册失败的链接
  24. failedRegistered.add(url);
  25. }
  26. }

如果注册失败,则记录在failedRegistered中,用于重试。我们重点关注doRegister(url),其实现在子类ZookeeperRegistry

  1. protected void doRegister(URL url) {
  2. try {
  3. // 通过 Zookeeper 客户端创建节点,节点路径由 toUrlPath 方法生成,路径格式如下:
  4. // /${group}/${serviceInterface}/providers/${url}
  5. // 比如
  6. // /dubbo/org.apache.dubbo.DemoService/providers/dubbo%3A%2F%2F127.0.0.1......
  7. zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
  8. } catch (Throwable e) {
  9. throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
  10. }
  11. }

继续看 zkClient.create 方法

  1. public void create(String path, boolean ephemeral) {
  2. if (!ephemeral) {
  3. // 如果要创建的节点类型非临时节点,那么这里要检测节点是否存在
  4. if(persistentExistNodePath.contains(path)){
  5. return;
  6. }
  7. if (checkExists(path)) {
  8. persistentExistNodePath.add(path);
  9. return;
  10. }
  11. }
  12. int i = path.lastIndexOf('/');
  13. if (i > 0) {
  14. // 递归创建上一级路径
  15. create(path.substring(0, i), false);
  16. }
  17. if (ephemeral) {
  18. createEphemeral(path);
  19. } else {
  20. createPersistent(path);
  21. persistentExistNodePath.add(path);
  22. }
  23. }

根据 ephemeral 参数判断,是创建临时节点还是永久节点。这里注意一下,path 的切割,是从后往前的。下面放上zookeeper的节点结构(可视化工具ZooInspector)

到这里,向注册中心注册服务也讲完了

总结

在Dubbo中,大量使用了Dubbo SPI机制(自动注入、自适应拓展),且很多地方都使用了模板模式。


参考资料

Dubbo开发指南

Dubbo源码(三) - 服务导出(生产者)的更多相关文章

  1. Dubbo 源码分析 - 服务导出

    1.服务导出过程 本篇文章,我们来研究一下 Dubbo 导出服务的过程.Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑.整个逻辑大致可 ...

  2. Dubbo源码阅读-服务导出

    Dubbo服务导出过程始于Spring容器发布刷新事件,Dubbo在接收到事件后,会立即执行服务导出逻辑.整个逻辑大致可分为三个部分,第一部分是前置工作,主要用于检查参数,组装URL.第二部分是导出服 ...

  3. Dubbo源码(四) - 服务引用(消费者)

    前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 上一篇文章,讲了Dubbo的服务导出: Dubbo源码(三) - 服务导出(生产者) 本文,咱们 ...

  4. Dubbo 源码分析 - 服务调用过程

    注: 本系列文章已捐赠给 Dubbo 社区,你也可以在 Dubbo 官方文档中阅读本系列文章. 1. 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与引入.以及集群容错方面的代码.经过 ...

  5. Dubbo源码(五) - 服务目录

    前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 今天,来聊聊Dubbo的服务目录(Directory).下面是官方文档对服务目录的定义: 服务目 ...

  6. Dubbo 源码分析 - 服务引用

    1. 简介 在上一篇文章中,我详细的分析了服务导出的原理.本篇文章我们趁热打铁,继续分析服务引用的原理.在 Dubbo 中,我们可以通过两种方式引用远程服务.第一种是使用服务直联的方式引用服务,第二种 ...

  7. Dubbo源码(九) - 服务调用过程

    1. 前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 源码分析均基于官方Demo,路径:dubbo/dubbo-demo 如果没有看过之前Dub ...

  8. Dubbo源码学习--服务发布(ServiceBean、ServiceConfig)

    前面讲过Dubbo SPI拓展机制,通过ExtensionLoader实现可插拔加载拓展,本节将接着分析Dubbo的服务发布过程. 以源码中dubbo-demo模块作为切入口一步步走进Dubbo源码. ...

  9. dubbo源码之服务发布与注册

    服务端发布流程: dubbo 是基于 spring 配置来实现服务的发布的,对于dubbo 配置文件中看到的<dubbo:service>等标签都是服务发布的重要配置 ,对于这些提供可配置 ...

随机推荐

  1. C#/VB.NET 在Excel单元格中应用多种字体格式

    在Excel中,可对单元格中的字符串设置多种不同样式,通常只需要获取到单元格直接设置样式即可,该方法设置的样式会应用于该单元格中的所有字符.如果需要对单元格中某些字符设置样式,则可以参考本文中的方法. ...

  2. 一篇文章说清 webpack、vite、vue-cli、create-vue 的区别

    webpack.vite.vue-cli.create-vue 这些都是什么?看着有点晕,不要怕,我们一起来分辨一下. 先看这个表格: 脚手架 vue-cli create-vue 构建项目 vite ...

  3. 聊聊 HTTPS

    聊聊 HTTPS 本文写于 2021 年 6 月 30 日 最近工作也是越来越忙了,不像上学的时候,一天下来闲着没事可以写两篇博客. 今天来聊一下 HTTPS. HTTP HTTP 是不安全的协议. ...

  4. 关于扑克牌的一些讨论——《Fluent Python 2》读书笔记

    一.说明 参考资料为维基百科的 Playing Card 词条,非严肃性论证,只是对代码为什么这么写做讨论. 二.扑克牌的起源 import collections Card = collection ...

  5. DirectX11 With Windows SDK--39 阴影技术(VSM、ESM)

    前言 上一章我们介绍了级联阴影贴图.刚开始的时候我尝试了给CSM直接加上PCSS,但不管怎么调难以达到说得过去的效果.然后文章越翻越觉得阴影就是一个巨大的坑,考虑到时间关系,本章只实现了方差阴影贴图( ...

  6. 解决mysq服务无法正常启动问题

    在mysql的启动过程中,遇到什么问题都可以反馈给我,我都会尽力帮你们解决 第一种:通过net start mysql启动MySQL服务器时,出现以下信息 是因为在MySQL5.7以上的版本中默认的没 ...

  7. spring-boot rest controller 使用枚举作为参数,重写反序列化实现任意值转枚举类型

    目录 BaseEnum MyEnum StringToEnumConverterFactory FormatterConfig DTO RestController 参考 BaseEnum packa ...

  8. SpringBoot项目使用jasypt加解密

    Jasypt 是一个 Java 库,它允许开发者以最小的努力为他 / 她的项目添加基本的加密功能,而且不需要对密码学的工作原理有深刻的了解. 一.添加依赖 <dependency> < ...

  9. 阿里云OSS + PicGo搭建图床

    1.阿里云 OSS 登录阿里云,进入控制台. 打开侧边栏,找到对象存储 OSS. 右侧找到 Bucket 管理,点击创建 Bucket. 根据引导配置 Bucket 其他同城冗余存储和版本控制等增值服 ...

  10. ACM 刷题记录

    HDU Multi-University Training Contest 题目来源 题目 知识点 时间复杂度 完成情况 2019 Contest8 A Acesrc and Cube Hyperne ...