转载

源码分析Dubbo服务提供者启动流程-上篇(文末附流程图)

本节将详细分析Dubbo服务提供者的启动流程,请带着如下几个疑问进行本节的阅读,因为这几个问题将是接下来几篇文章分析的重点内容。

  1. 什么时候建立与注册中心的连接。
  2. 服务提供者什么时候向注册中心注册服务。
  3. 服务提供者与注册中心的心跳机制。

从上文中我们得知,服务提供者启动的核心入口为ServiceBean,本节将从源码级别详细剖析ServcieBean的实现原理,即Dubbo服务提供者的启动流程,ServiceBean的继承层次如图所示,dubbo:service标签的所有属性都被封装在此类图结构中。

源码分析Dubbo服务提供者启动流程-上篇(文末附流程图)

1、源码分析ServiceBean#afterPropertiesSet

ServiceBean#afterPropertiesSet

if (getProvider() == null) {  // @1
    Map<String, ProviderConfig> provide
             ConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class,   false, false); // @2
              // ......  具体解析代码省略。
    }
}
复制代码

Step1:如果provider为空,说明dubbo:service标签未设置provider属性,如果一个dubbo:provider标签,则取该实例,如果存在多个dubbo:provider配置则provider属性不能为空,否则抛出异常:"Duplicate provider configs"。

ServiceBean#afterPropertiesSet

if (getApplication() == null
         && (getProvider() == null || getProvider().getApplication() == null)) {
       Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 
                  ApplicationConfig.class, false, false);
        // ...省略
 }
复制代码

Step2:如果application为空,则尝试从BeanFactory中查询dubbo:application实例,如果存在多个dubbo:application配置,则抛出异常:"Duplicate application configs"。 Step3:如果ServiceBean的module为空,则尝试从BeanFactory中查询dubbo:module实例,如果存在多个dubbo:module,则抛出异常:"Duplicate module configs: "。 Step4:尝试从BeanFactory中加载所有的注册中心,注意ServiceBean的List< RegistryConfig> registries属性,为注册中心集合。 Step5:尝试从BeanFacotry中加载一个监控中心,填充ServiceBean的MonitorConfig monitor属性,如果存在多个dubbo:monitor配置,则抛出"Duplicate monitor configs: "。 Step6:尝试从BeanFactory中加载所有的协议,注意:ServiceBean的List< ProtocolConfig> protocols是一个集合,也即一个服务可以通过多种协议暴露给消费者。

ServiceBean#afterPropertiesSet

if (getPath() == null || getPath().length() == 0) {
       if (beanName != null && beanName.length() > 0 && getInterface() != null && getInterface().length() > 0  && beanName.startsWith(getInterface())) {
                setPath(beanName);
       }
 }
复制代码

Step7:设置ServiceBean的path属性,path属性存放的是dubbo:service的beanName(dubbo:service id)。

ServiceBean#afterPropertiesSet

if (!isDelay()) {
     export();
}
复制代码

Step8:如果为启用延迟暴露机制,则调用export暴露服务。首先看一下isDelay的实现,然后重点分析export的实现原理(服务暴露的整个实现原理)。

ServiceBean#isDelay

private boolean isDelay() {
        Integer delay = getDelay();
        ProviderConfig provider = getProvider();
        if (delay == null && provider != null) {
            delay = provider.getDelay();
        }
        return supportedApplicationListener && (delay == null || delay == -1);
    }
复制代码

如果有设置dubbo:service或dubbo:provider的属性delay,或配置delay为-1,都表示启用延迟机制,单位为毫秒,设置为-1,表示等到Spring容器初始化后再暴露服务。从这里也可以看出,Dubbo暴露服务的处理入口为ServiceBean#export---》ServiceConfig#export。

1.1 源码分析ServiceConfig#export 暴露服务

调用链:ServiceBean#afterPropertiesSet------>ServiceConfig#export

public synchronized void export() {
        if (provider != null) {
            if (export == null) {
                export = provider.getExport();
            }
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        if (export != null && !export) {   // @1
            return;
        }

        if (delay != null && delay > 0) {    // @2
            delayExportExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        } else {
            doExport();    //@3
        }
    }
复制代码

代码@1:判断是否暴露服务,由dubbo:service export="true|false"来指定。 代码@2:如果启用了delay机制,如果delay大于0,表示延迟多少毫秒后暴露服务,使用ScheduledExecutorService延迟调度,最终调用doExport方法。 代码@3:执行具体的暴露逻辑doExport,需要大家留意:delay=-1的处理逻辑(基于Spring事件机制触发)。

1.2 源码分析ServiceConfig#doExport暴露服务

调用链:ServiceBean#afterPropertiesSet---调用------>ServiceConfig#export------>ServiceConfig#doExport

ServiceConfig#checkDefault

private void checkDefault() {
        if (provider == null) {
            provider = new ProviderConfig();
        }
        appendProperties(provider);
 }
复制代码

Step1:如果dubbo:servce标签也就是ServiceBean的provider属性为空,调用appendProperties方法,填充默认属性,其具体加载顺序:

  1. 从系统属性加载对应参数值,参数键:dubbo.provider.属性名,System.getProperty。
  2. 加载属性配置文件的值。属性配置文件,可通过系统属性:dubbo.properties.file,如果该值未配置,则默认取dubbo.properties属性配置文件。

ServiceConfig#doExport

if (ref instanceof GenericService) {
      interfaceClass = GenericService.class;
      if (StringUtils.isEmpty(generic)) {
           generic = Boolean.TRUE.toString();
      }
 } else {
      try {
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
       } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
       }
       checkInterfaceAndMethods(interfaceClass, methods);
       checkRef();
       generic = Boolean.FALSE.toString();
 }
复制代码

Step2:校验ref与interface属性。如果ref是GenericService,则为dubbo的泛化实现,然后验证interface接口与ref引用的类型是否一致。

ServiceConfig#doExport

if (local != null) {
      if ("true".equals(local)) {
            local = interfaceName + "Local";
      }
      Class<?> localClass;
      try {
             localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
       } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
       }
      if (!interfaceClass.isAssignableFrom(localClass)) {
           throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
       }
 }
复制代码

Step3:dubbo:service local机制,已经废弃,被stub属性所替换。 Step4:处理本地存根Stub,<dubbo:service 的stub属性,可以设置为true,此时Stub的类名为:interface+Stub,stub也可以指定自定义的全类名。本地存根说明如图所示(Dubbo官方文档)

源码分析Dubbo服务提供者启动流程-上篇(文末附流程图)

ServiceConfig#doExport

checkApplication();
checkRegistry();
checkProtocol();
appendProperties(this);
复制代码

Step5:校验ServiceBean的application、registry、protocol是否为空,并从系统属性(优先)、资源文件中填充其属性。 系统属性、资源文件属性的配置如下: application dubbo.application.属性名,例如 dubbo.application.name registry dubbo.registry.属性名,例如 dubbo.registry.address protocol dubbo.protocol.属性名,例如 dubbo.protocol.port service dubbo.service.属性名,例如 dubbo.service.stub

ServiceConfig#doExport

checkStubAndMock(interfaceClass);
复制代码

Step6:校验stub、mock类的合理性,是否是interface的实现类。

ServiceConfig#doExport

doExportUrls();
复制代码

Step7:执行doExportUrls()方法暴露服务,接下来会重点分析该方法。

ServiceConfig#doExport

ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
复制代码

Step8:将服务提供者信息注册到ApplicationModel实例中。

1.3 源码分析ServiceConfig#doExportUrls暴露服务具体实现逻辑

调用链:ServiceBean#afterPropertiesSet------>ServiceConfig#export------>ServiceConfig#doExport

private void doExportUrls() {
        List<URL> registryURLs = loadRegistries(true);  // @1
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);    // @2
        }
 }
复制代码

代码@1:首先遍历ServiceBean的List< RegistryConfig> registries(所有注册中心的配置信息),然后将地址封装成URL对象,关于注册中心的所有配置属性,最终转换成url的属性(?属性名=属性值),loadRegistries(true),参数的意思:true,代表服务提供者,false:代表服务消费者,如果是服务提供者,则检测注册中心的配置,如果配置了register="false",则忽略该地址,如果是服务消费者,并配置了subscribe="false"则表示不从该注册中心订阅服务,故也不返回,一个注册中心URL示例: registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&pid=7072&qos.port=22222&registry=zookeeper&timestamp=1527308268041 代码@2:然后遍历配置的所有协议,根据每个协议,向注册中心暴露服务,接下来重点分析doExportUrlsFor1Protocol方法的实现细节。

1.4 源码分析doExportUrlsFor1Protocol

调用链:ServiceBean#afterPropertiesSet------>ServiceConfig#export------>ServiceConfig#doExport------>ServiceConfig#doExportUrlsFor1Protocol ServiceConfig#doExportUrlsFor1Protocol

String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
     name = "dubbo";
}
Map<String, String> map = new HashMap<String, String>();
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
    map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, protocolConfig);
appendParameters(map, this);
复制代码

Step1:用Map存储该协议的所有配置参数,包括协议名称、dubbo版本、当前系统时间戳、进程ID、application配置、module配置、默认服务提供者参数(ProviderConfig)、协议配置、服务提供Dubbo:service的属性。

ServiceConfig#doExportUrlsFor1Protocol

if (methods != null && !methods.isEmpty()) {
            for (MethodConfig method : methods) {
                appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                List<ArgumentConfig> arguments = method.getArguments();
                if (arguments != null && !arguments.isEmpty()) {
                    for (ArgumentConfig argument : arguments) {
                        // convert argument type
                        if (argument.getType() != null && argument.getType().length() > 0) {
                            Method[] methods = interfaceClass.getMethods();
                            // visit all methods
                            if (methods != null && methods.length > 0) {
                                for (int i = 0; i < methods.length; i++) {
                                    String methodName = methods[i].getName();
                                    // target the method, and get its signature
                                    if (methodName.equals(method.getName())) {
                                        Class<?>[] argtypes = methods[i].getParameterTypes();
                                        // one callback in the method
                                        if (argument.getIndex() != -1) {
                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                                appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                            } else {
                                                throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + 
                                                      argument.getType());
                                            }
                                        } else {
                                            // multiple callbacks in the method
                                            for (int j = 0; j < argtypes.length; j++) {
                                                Class<?> argclazz = argtypes[j];
                                                if (argclazz.getName().equals(argument.getType())) {
                                                    appendParameters(map, argument, method.getName() + "." + j);
                                                    if (argument.getIndex() != -1 && argument.getIndex() != j) {
                                                        throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", 
                                                             type:" + argument.getType());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        } else if (argument.getIndex() != -1) {
                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                        } else {
                            throw new IllegalArgumentException("argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
                        }

                    }
                }
            } // end of methods for
        }
复制代码

Step2:如果dubbo:service有dubbo:method子标签,则dubbo:method以及其子标签的配置属性,都存入到Map中,属性名称加上对应的方法名作为前缀。dubbo:method的子标签dubbo:argument,其键为方法名.参数序号。

ServiceConfig#doExportUrlsFor1Protocol

if (ProtocolUtils.isGeneric(generic)) {
      map.put(Constants.GENERIC_KEY, generic);
      map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
 } else {
      String revision = Version.getVersion(interfaceClass, version);
      if (revision != null && revision.length() > 0) {
          map.put("revision", revision);
      }
      String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
      if (methods.length == 0) {
           logger.warn("NO method found in service interface " + interfaceClass.getName());
           map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
      } else {
           map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
      }
}
复制代码

Step3:添加methods键值对,存放dubbo:service的所有方法名,多个方法名用,隔开,如果是泛化实现,填充genric=true,methods为"*";

ServiceConfig#doExportUrlsFor1Protocol

if (!ConfigUtils.isEmpty(token)) {
      if (ConfigUtils.isDefault(token)) {
            map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
       } else {
            map.put(Constants.TOKEN_KEY, token);
       }
}
复制代码

Step4:根据是否开启令牌机制,如果开启,设置token键,值为静态值或uuid。

ServiceConfig#doExportUrlsFor1Protocol

if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
       protocolConfig.setRegister(false);
       map.put("notify", "false");
}
复制代码

Step5:如果协议为本地协议(injvm),则设置protocolConfig#register属性为false,表示不向注册中心注册服务,在map中存储键为notify,值为false,表示当注册中心监听到服务提供者发送变化(服务提供者增加、服务提供者减少等事件时不通知。

ServiceConfig#doExportUrlsFor1Protocol

// export service
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
     contextPath = provider.getContextpath();
}
复制代码

Step6:设置协议的contextPath,如果未配置,默认为/interfacename

ServiceConfig#doExportUrlsFor1Protocol

String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = this.findConfigedPorts(protocolConfig, name, map);
复制代码

Step7:解析服务提供者的IP地址与端口。 服务IP地址解析顺序:(序号越小越优先)

  1. 系统环境变量,变量名:DUBBO_DUBBO_IP_TO_BIND
  2. 系统属性,变量名:DUBBO_DUBBO_IP_TO_BIND
  3. 系统环境变量,变量名:DUBBO_IP_TO_BIND
  4. 系统属性,变量名:DUBBO_IP_TO_BIND
  5. dubbo:protocol 标签的host属性 --》 dubbo:provider 标签的host属性
  6. 默认网卡IP地址,通过InetAddress.getLocalHost().getHostAddress()获取,如果IP地址不符合要求,继续下一个匹配。

判断IP地址是否符合要求的标准是:

public static boolean isInvalidLocalHost(String host) {
        return host == null
                || host.length() == 0
                || host.equalsIgnoreCase("localhost")
                || host.equals("0.0.0.0")
                || (LOCAL_IP_PATTERN.matcher(host).matches());
      }
复制代码
  1. 选择第一个可用网卡,其实现方式是建立socket,连接注册中心,获取socket的IP地址。其代码:
Socket socket = new Socket();
        try {
              SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
              socket.connect(addr, 1000);
              hostToBind = socket.getLocalAddress().getHostAddress();
              break;
         } finally {
              try {
                      socket.close();
              } catch (Throwable e) {
              }
        }
复制代码

服务提供者端口解析顺序:(序号越小越优先)

  1. 系统环境变量,变量名:DUBBO_DUBBO_PORT_TO_BIND
  2. 系统属性,变量名:DUBBO_DUBBO_PORT_TO_BIND
  3. 系统环境变量,变量名:DUBBO_PORT_TO_BIND
  4. 系统属性,变量名DUBBO_PORT_TO_BIND
  5. dubbo:protocol标签port属性、dubbo:provider标签的port属性。
  6. 随机选择一个端口。

ServiceConfig#doExportUrlsFor1Protocol

URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
复制代码

Step8:根据协议名称、协议host、协议端口、contextPath、相关配置属性(application、module、provider、protocolConfig、service及其子标签)构建服务提供者URI。 URL运行效果图:

源码分析Dubbo服务提供者启动流程-上篇(文末附流程图)

以dubbo协议为例,展示最终服务提供者的URL信息如下:dubbo://192.168.56.1:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.56.1&bind.port=20880&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=5916&qos.port=22222&side=provider&timestamp=1527168070857

ServiceConfig#doExportUrlsFor1Protocol

String scope = url.getParameter(Constants.SCOPE_KEY);
// don't export when none is configured
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
     // 接口暴露实现逻辑
}
复制代码

Step9:获取dubbo:service标签的scope属性,其可选值为none(不暴露)、local(本地)、remote(远程),如果配置为none,则不暴露。默认为local。

ServiceConfig#doExportUrlsFor1Protocol

// export to local if the config is not remote (export to remote only when config is remote)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {     // @1
       exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {     // @2
        if (logger.isInfoEnabled()) {
              logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
         }
        if (registryURLs != null && !registryURLs.isEmpty()) {   // @3
              for (URL registryURL : registryURLs) {
                    url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));  // @4
                    URL monitorUrl = loadMonitor(registryURL);       // @5
                    if (monitorUrl != null) {
                        url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());  
                    }
                    if (logger.isInfoEnabled()) {
                        logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                    }
                   Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));  // @6
                   DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);    
                   Exporter<?> exporter = protocol.export(wrapperInvoker);    // @7
                   exporters.add(exporter);
               }
         } else {
               Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
               DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
               Exporter<?> exporter = protocol.export(wrapperInvoker);
               exporters.add(exporter);
         }
}
复制代码

Step10:根据scope来暴露服务,如果scope不配置,则默认本地与远程都会暴露,如果配置成local或remote,那就只能是二选一。 代码@1:如果scope不为remote,则先在本地暴露(injvm):,具体暴露服务的具体实现,将在remote 模式中详细分析。 代码@2:如果scope不为local,则将服务暴露在远程。 代码@3:remote方式,检测当前配置的所有注册中心,如果注册中心不为空,则遍历注册中心,将服务依次在不同的注册中心进行注册。 代码@4:如果dubbo:service的dynamic属性未配置, 尝试取dubbo:registry的dynamic属性,该属性的作用是否启用动态注册,如果设置为false,服务注册后,其状态显示为disable,需要人工启用,当服务不可用时,也不会自动移除,同样需要人工处理,此属性不要在生产环境上配置。 代码@5:根据注册中心url(注册中心url),构建监控中心的URL,如果监控中心URL不为空,则在服务提供者URL上追加monitor,其值为监控中心url(已编码)。

  • 如果dubbo spring xml配置文件中没有配置监控中心(dubbo:monitor),如果从系统属性-Ddubbo.monitor.address,-Ddubbo.monitor.protocol构建MonitorConfig对象,否则从dubbo的properties配置文件中寻找这个两个参数,如果没有配置,则返回null。
  • 如果有配置,则追加相关参数,dubbo:monitor标签只有两个属性:address、protocol,其次会追加interface(MonitorService)、协议等。

代码@6:通过动态代理机制创建Invoker,dubbo的远程调用实现类。

源码分析Dubbo服务提供者启动流程-上篇(文末附流程图)

Dubbo远程调用器如何构建,这里不详细深入,重点关注WrapperInvoker的url为:registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&export=dubbo%3A%2F%2F192.168.56.1%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bind.ip%3D192.168.56.1%26bind.port%3D20880%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D6328%26qos.port%3D22222%26side%3Dprovider%26timestamp%3D1527255510215&pid=6328&qos.port=22222&registry=zookeeper&timestamp=1527255510202,这里有两个重点值得关注:

  • path属性:com.alibaba.dubbo.registry.RegistryService,注册中心也类似于服务提供者。
  • export属性:值为服务提供者的URL,为什么需要关注这个URL呢?请看代码@7,protocol属性为Protocol$Adaptive,Dubbo在加载组件实现类时采用SPI(插件机制,有关于插件机制,在该专题后续文章将重点分析),在这里我们只需要知道,根据URL冒号之前的协议名将会调用相应的方法。
    源码分析Dubbo服务提供者启动流程-上篇(文末附流程图)
    其映射关系(列出与服务启动相关协议实现类): dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol //文件位于dubbo-rpc-dubbo/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol registry=com.alibaba.dubbo.registry.integration.RegistryProtocol //文件位于dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol 代码@7:根据代码@6的分析,将调用RegistryProtocol#export方法。

1.5 源码分析RegistryProtocol#export方法

调用链:ServiceBean#afterPropertiesSet------>ServiceConfig#export------>ServiceConfig#doExport------>ServiceConfig#doExportUrlsFor1Protocol------>RegistryProtocol#export

RegistryProtocol#export

@Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        //export invoker
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);   // @1

        URL registryUrl = getRegistryUrl(originInvoker);       // @2

        //registry provider
        final Registry registry = getRegistry(originInvoker);                          // @3
        final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);     // @4start

        //to judge to delay publish whether or not
        boolean register = registedProviderUrl.getParameter("register", true);  

        ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);

        if (register) {  
            register(registryUrl, registedProviderUrl);      // @4 end
            ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
        }

        // Subscribe the override data
        // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call the same service. Because the subscribed is cached key with the name of the service, it causes the subscription information to cover.
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);          // @5 start
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);            // @5 end
        //Ensure that a new exporter instance is returned every time export
        return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
    }
复制代码

代码@1:启动服务提供者服务,监听指定端口,准备服务消费者的请求,这里其实就是从WrapperInvoker中的url(注册中心url)中提取export属性,描述服务提供者的url,然后启动服务提供者。

源码分析Dubbo服务提供者启动流程-上篇(文末附流程图)

从上图中,可以看出,将调用DubboProtocol#export完成dubbo服务的启动,利用netty构建一个微型服务端,监听端口,准备接受服务消费者的网络请求,本节旨在梳理其启动流程,具体实现细节,将在后续章节中详解,这里我们只要知道,< dubbo:protocol name="dubbo" port="20880" />,会再此次监听该端口,然后将dubbo:service的服务handler加入到命令处理器中,当有消息消费者连接该端口时,通过网络解包,将需要调用的服务和参数等信息解析处理后,转交给对应的服务实现类处理即可。 代码@2:获取真实注册中心的URL,例如zookeeper注册中心的URL:zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&export=dubbo%3A%2F%2F192.168.56.1%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bind.ip%3D192.168.56.1%26bind.port%3D20880%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D10252%26qos.port%3D22222%26side%3Dprovider%26timestamp%3D1527263060882&pid=10252&qos.port=22222&timestamp=1527263060867 代码@3:根据注册中心URL,从注册中心工厂中获取指定的注册中心实现类:zookeeper注册中心的实现类为:ZookeeperRegistry 代码@4:获取服务提供者URL中的register属性,如果为true,则调用注册中心的ZookeeperRegistry#register方法向注册中心注册服务(实际由其父类FailbackRegistry实现)。 代码@5:服务提供者向注册中心订阅自己,主要是为了服务提供者URL发送变化后重新暴露服务,当然,会将dubbo:reference的check属性设置为false。

到这里就对文章开头提到的问题1,问题2做了一个解答,其与注册中心的心跳机制等将在后续章节中详细分析。

文字看起来可能不是很直观,现整理一下Dubbo服务提供者启动流程图如下:

源码分析Dubbo服务提供者启动流程-上篇(文末附流程图)

本文重点梳理了Dubbo服务提供者启动流程,其中Dubbo服务提供者在指定端口监听服务的启动流程将在下一节中详细分析。

作者介绍:丁威,《RocketMQ技术内幕》作者,RocketMQ 社区布道师,公众号: 中间件兴趣圈 维护者,目前已陆续发表源码分析Java集合、Java 并发包(JUC)、Netty、Mycat、Dubbo、RocketMQ、Mybatis等源码专栏。可以点击链接加入 中间件知识星球 ,一起探讨高并发、分布式服务架构,交流源码。

源码分析Dubbo服务提供者启动流程-上篇(文末附流程图)
原文  https://juejin.im/post/5df8d0b8f265da339b4ffd83
正文到此结束
Loading...