源码分析Dubbo服务提供者启动流程-上篇

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

转载声明:转载请注明出处,本技术博客是本人原创文章

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 源码分析Dubbo服务提供者启动流程-上篇

微信公众号:**[中间件兴趣圈]**

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

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

    源码分析Dubbo服务提供者启动流程-上篇

    源码分析ServiceBean

    ServiceBean#afterPropertiesSet

    
    1if (getProvider() == null) {  // @1
    2    MapString, ProviderConfig provide
    3             ConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class,   false, false); // @2
    4              // ......  具体解析代码省略。
    5    }
    6}
    

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

    ServiceBean#afterPropertiesSet

    
    1 if (getApplication() == null
    2         && (getProvider() == null || getProvider().getApplication() == null)) {
    3       MapString, ApplicationConfig applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 
    4                  ApplicationConfig.class, false, false);
    5        // ...省略
    6 }
    

    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

    
    1if (getPath() == null || getPath().length() == 0) {
    2       if (beanName != null && beanName.length()  0 && getInterface() != null && getInterface().length()  0  && beanName.startsWith(getInterface())) {
    3                setPath(beanName);
    4       }
    5 }
    

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

    ServiceBean#afterPropertiesSet

    
    1if (!isDelay()) {
    2     export();
    3}
    

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

    ServiceBean#isDelay

    
    1private boolean isDelay() {
    2        Integer delay = getDelay();
    3        ProviderConfig provider = getProvider();
    4        if (delay == null && provider != null) {
    5            delay = provider.getDelay();
    6        }
    7        return supportedApplicationListener && (delay == null || delay == -1);
    8    }
    

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

    源码分析ServiceConfig#export暴露服务

    调用链:ServiceBean#afterPropertiesSet-ServiceConfig#export

    
     1public synchronized void export() {
     2        if (provider != null) {
     3            if (export == null) {
     4                export = provider.getExport();
     5            }
     6            if (delay == null) {
     7                delay = provider.getDelay();
     8            }
     9        }
    10        if (export != null && !export) {   // @1
    11            return;
    12        }
    13
    14        if (delay != null && delay  0) {    // @2
    15            delayExportExecutor.schedule(new Runnable() {
    16                @Override
    17                public void run() {
    18                    doExport();
    19                }
    20            }, delay, TimeUnit.MILLISECONDS);
    21        } else {
    22            doExport();    //@3
    23        }
    24    }
    

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

    源码分析ServiceConfig#doExport暴露服务

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

    ServiceConfig#checkDefault

    
    1private void checkDefault() {
    2        if (provider == null) {
    3            provider = new ProviderConfig();
    4        }
    5        appendProperties(provider);
    6 }
    

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

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

    
     1if (ref instanceof GenericService) {
     2      interfaceClass = GenericService.class;
     3      if (StringUtils.isEmpty(generic)) {
     4           generic = Boolean.TRUE.toString();
     5      }
     6 } else {
     7      try {
     8            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
     9                        .getContextClassLoader());
    10       } catch (ClassNotFoundException e) {
    11            throw new IllegalStateException(e.getMessage(), e);
    12       }
    13       checkInterfaceAndMethods(interfaceClass, methods);
    14       checkRef();
    15       generic = Boolean.FALSE.toString();
    16 }
    

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

    ServiceConfig#doExport

    
     1if (local != null) {
     2      if ("true".equals(local)) {
     3            local = interfaceName + "Local";
     4      }
     5      Class? localClass;
     6      try {
     7             localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
     8       } catch (ClassNotFoundException e) {
     9            throw new IllegalStateException(e.getMessage(), e);
    10       }
    11      if (!interfaceClass.isAssignableFrom(localClass)) {
    12           throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
    13       }
    14 }
    

    Step3:dubbo:service local机制,已经废弃,被stub属性所替换。

    Step4:处理本地存根Stub(图片来源于Dubbo官方文档)。

    源码分析Dubbo服务提供者启动流程-上篇

    ServiceConfig#doExport

    
    1checkApplication();
    2checkRegistry();
    3checkProtocol();
    4appendProperties(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
  • registry          dubbo.registry.属性名,例如    dubbo.registry.address

    service           dubbo.service.属性名,例如 dubbo.service.stub

    ServiceConfig#doExport

    
    1checkStubAndMock(interfaceClass);
    

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

    ServiceConfig#doExport

    
    1doExportUrls();
    

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

    ServiceConfig#doExport

    
    1ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
    2ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
    

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

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

    调用链:ServiceBean#afterPropertiesSet-ServiceConfig#exportServiceConfig#doExport

    
    1private void doExportUrls() {
    2        ListURL registryURLs = loadRegistries(true);  // @1
    3        for (ProtocolConfig protocolConfig : protocols) {
    4            doExportUrlsFor1Protocol(protocolConfig, registryURLs);    // @2
    5        }
    6 }
    

    代码@1:首先遍历ServiceBean的List RegistryConfig registries(所有注册中心的配置信息),然后将地址封装成URL对象,关于注册中心的所有配置属性,最终转换成url的属性(?属性名=属性值),loadRegistries(true),参数的意思:true,代表服务提供者,false:代表服务消费者,如果是服务提供者,则检测注册中心的配置,如果配置了register=”false”,则忽略该地址,如果是服务消费者,并配置了subscribe=”false”则表示不从该注册中心订阅服务,故也不返回,一个注册中心URL示例:

    
    1registry://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方法的实现细节。

    源码分析doExportUrlsFor1Protocol

    调用链:ServiceBean#afterPropertiesSet-ServiceConfig#exportServiceConfig#doExport-ServiceConfig#doExportUrlsFor1Protocol

    ServiceConfig#doExportUrlsFor1Protocol

    
     1String name = protocolConfig.getName();
     2if (name == null || name.length() == 0) {
     3     name = "dubbo";
     4}
     5MapString, String map = new HashMapString, String();
     6map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
     7map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
     8map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
     9if (ConfigUtils.getPid()  0) {
    10    map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
    11}
    12appendParameters(map, application);
    13appendParameters(map, module);
    14appendParameters(map, provider, Constants.DEFAULT_KEY);
    15appendParameters(map, protocolConfig);
    16appendParameters(map, this);
    

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

    ServiceConfig#doExportUrlsFor1Protocol

    
     1if (methods != null && !methods.isEmpty()) {
     2            for (MethodConfig method : methods) {
     3                appendParameters(map, method, method.getName());
     4                String retryKey = method.getName() + ".retry";
     5                if (map.containsKey(retryKey)) {
     6                    String retryValue = map.remove(retryKey);
     7                    if ("false".equals(retryValue)) {
     8                        map.put(method.getName() + ".retries", "0");
     9                    }
    10                }
    11                ListArgumentConfig arguments = method.getArguments();
    12                if (arguments != null && !arguments.isEmpty()) {
    13                    for (ArgumentConfig argument : arguments) {
    14                        // convert argument type
    15                        if (argument.getType() != null && argument.getType().length()  0) {
    16                            Method[] methods = interfaceClass.getMethods();
    17                            // visit all methods
    18                            if (methods != null && methods.length  0) {
    19                                for (int i = 0; i  methods.length; i++) {
    20                                    String methodName = methods[i].getName();
    21                                    // target the method, and get its signature
    22                                    if (methodName.equals(method.getName())) {
    23                                        Class?[] argtypes = methods[i].getParameterTypes();
    24                                        // one callback in the method
    25                                        if (argument.getIndex() != -1) {
    26                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
    27                                                appendParameters(map, argument, method.getName() + "." + argument.getIndex());
    28                                            } else {
    29                                                throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + 
    30                                                      argument.getType());
    31                                            }
    32                                        } else {
    33                                            // multiple callbacks in the method
    34                                            for (int j = 0; j  argtypes.length; j++) {
    35                                                Class? argclazz = argtypes[j];
    36                                                if (argclazz.getName().equals(argument.getType())) {
    37                                                    appendParameters(map, argument, method.getName() + "." + j);
    38                                                    if (argument.getIndex() != -1 && argument.getIndex() != j) {
    39                                                        throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", 
    40                                                             type:" + argument.getType());
    41                                                    }
    42                                                }
    43                                            }
    44                                        }
    45                                    }
    46                                }
    47                            }
    48                        } else if (argument.getIndex() != -1) {
    49                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
    50                        } else {
    51                            throw new IllegalArgumentException("argument config must set index or type attribute.eg: dubbo:argument index='0' .../ or dubbo:argument type=xxx .../");
    52                        }
    53
    54                    }
    55                }
    56            } // end of methods for
    57        }
    

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

    ServiceConfig#doExportUrlsFor1Protocol

    
     1if (ProtocolUtils.isGeneric(generic)) {
     2      map.put(Constants.GENERIC_KEY, generic);
     3      map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
     4 } else {
     5      String revision = Version.getVersion(interfaceClass, version);
     6      if (revision != null && revision.length()  0) {
     7          map.put("revision", revision);
     8      }
     9      String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
    10      if (methods.length == 0) {
    11           logger.warn("NO method found in service interface " + interfaceClass.getName());
    12           map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
    13      } else {
    14           map.put(Constants.METHODS_KEY, StringUtils.join(new HashSetString(Arrays.asList(methods)), ","));
    15      }
    16}
    

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

    ServiceConfig#doExportUrlsFor1Protocol

    
    1if (!ConfigUtils.isEmpty(token)) {
    2      if (ConfigUtils.isDefault(token)) {
    3            map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
    4       } else {
    5            map.put(Constants.TOKEN_KEY, token);
    6       }
    7}
    

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

    ServiceConfig#doExportUrlsFor1Protocol

    
    1if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
    2       protocolConfig.setRegister(false);
    3       map.put("notify", "false");
    4}
    

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

    ServiceConfig#doExportUrlsFor1Protocol

    
    1// export service
    2String contextPath = protocolConfig.getContextpath();
    3if ((contextPath == null || contextPath.length() == 0) && provider != null) {
    4     contextPath = provider.getContextpath();
    5}
    

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

    ServiceConfig#doExportUrlsFor1Protocol

    
    1String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
    2Integer port = this.findConfigedPorts(protocolConfig, name, map);
    

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

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

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

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

  • 系统环境变量,变量名:DUBBO_DUBBO_PORT_TO_BIND
  • 系统属性,变量名:DUBBO_DUBBO_PORT_TO_BIND
  • 系统环境变量,变量名:DUBBO_PORT_TO_BIND
  • 系统属性,变量名DUBBO_PORT_TO_BIND
  • dubbo:protocol标签port属性、dubbo:provider标签的port属性。
  • 随机选择一个端口。
  • ServiceConfig#doExportUrlsFor1Protocol

    
    1URL 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

    
    1String scope = url.getParameter(Constants.SCOPE_KEY);
    2// don't export when none is configured
    3if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
    4     // 接口暴露实现逻辑
    5}
    

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

    ServiceConfig#doExportUrlsFor1Protocol

    
     1// export to local if the config is not remote (export to remote only when config is remote)
     2if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {     // @1
     3       exportLocal(url);
     4}
     5// export to remote if the config is not local (export to local only when config is local)
     6if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {     // @2
     7        if (logger.isInfoEnabled()) {
     8              logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
     9         }
    10        if (registryURLs != null && !registryURLs.isEmpty()) {   // @3
    11              for (URL registryURL : registryURLs) {
    12                    url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));  // @4
    13                    URL monitorUrl = loadMonitor(registryURL);       // @5
    14                    if (monitorUrl != null) {
    15                        url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());  
    16                    }
    17                    if (logger.isInfoEnabled()) {
    18                        logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
    19                    }
    20                   Invoker? invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));  // @6
    21                   DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);    
    22                   Exporter? exporter = protocol.export(wrapperInvoker);    // @7
    23                   exporters.add(exporter);
    24               }
    25         } else {
    26               Invoker? invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
    27               DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
    28               Exporter? exporter = protocol.export(wrapperInvoker);
    29               exporters.add(exporter);
    30         }
    31}
    

    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远程调用器如何构建,这里不详细深入,重点关注WrapperInvoker的url为:
  • 源码分析Dubbo服务提供者启动流程-上篇
  • 如果有配置,则追加相关参数,dubbo:monitor标签只有两个属性:address、protocol,其次会追加interface(MonitorService)、协议等。
    代码@6:通过动态代理机制创建Invoker,dubbo的远程调用实现类。

    
    1registry://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服务提供者启动流程-上篇
  • export属性:值为服务提供者的URL,为什么需要关注这个URL呢?请看代码@7,protocol属性为Protocol$Adaptive,Dubbo在加载组件实现类时采用SPI(插件机制,有关于插件机制,在该专题后续文章将重点分析),在这里我们只需要知道,根据URL冒号之前的协议名将会调用相应的方法。

    
    1dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol       //文件位于dubbo-rpc-dubbo/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
    2registry=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方法。

    源码分析RegistryProtocol#export方法

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

    RegistryProtocol#export

    
     1@Override
     2    public  Exporter export(final Invoker originInvoker) throws RpcException {
     3        //export invoker
     4        final ExporterChangeableWrapper exporter = doLocalExport(originInvoker);   // @1
     5
     6        URL registryUrl = getRegistryUrl(originInvoker);       // @2
     7
     8        //registry provider
     9        final Registry registry = getRegistry(originInvoker);                          // @3
    10        final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);     // @4start
    11
    12        //to judge to delay publish whether or not
    13        boolean register = registedProviderUrl.getParameter("register", true);  
    14
    15        ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);
    16
    17        if (register) {  
    18            register(registryUrl, registedProviderUrl);      // @4 end
    19            ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
    20        }
    21
    22        // Subscribe the override data
    23        // 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.
    24        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);          // @5 start
    25        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    26        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
    27        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);            // @5 end
    28        //Ensure that a new exporter instance is returned every time export
    29        return new DestroyableExporter(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
    30    }
    

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

    源码分析Dubbo服务提供者启动流程-上篇
    
    1URL: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技术内幕》已上市

    源码分析Dubbo服务提供者启动流程-上篇

    《RocketMQ技术内幕》已出版上市,目前可在主流购物平台(京东、天猫等)购买,本书从源码角度深度分析了RocketMQ NameServer、消息发送、消息存储、消息消费、消息过滤、主从同步HA、事务消息;在实战篇重点介绍了RocketMQ运维管理界面与当前支持的39个运维命令;并在附录部分罗列了RocketMQ几乎所有的配置参数。本书得到了RocketMQ创始人、阿里巴巴Messaging开源技术负责人、Linux OpenMessaging 主席的高度认可并作序推荐。目前是国内第一本成体系剖析RocketMQ的书籍。
    新书7折优惠!7折优惠!7折优惠!

    更多文章请关注微信公众号:

    源码分析Dubbo服务提供者启动流程-上篇

    推荐关注微信公众号:RocketMQ官方微信公众号。

    源码分析Dubbo服务提供者启动流程-上篇

    原文始发于微信公众号(中间件兴趣圈):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    转载声明:转载请注明出处,本技术博客是本人原创文章

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> 源码分析Dubbo服务提供者启动流程-上篇


     上一篇
    源码分析Dubbo服务提供者启动流程-下篇 源码分析Dubbo服务提供者启动流程-下篇
    微信公众号:**[中间件兴趣圈]** 作者简介:《RocketMQ技术内幕》作者; 本文继续上文Dubbo服务提供者启动流程,在上篇文章中详细梳理了基于dubbo spring文件的配置方式,Dubbo是如何加载配置文件,服务提供者
    2021-04-05
    下一篇 
    源码分析Dubbo前置篇-寻找注册中心、服务提供者、服务消费者功能入口 源码分析Dubbo前置篇-寻找注册中心、服务提供者、服务消费者功能入口
    本节主要阐述如下两个问题: Dubbo自定义标签实现。 dubbo通过Spring加载配置文件后,是如何触发注册中心、服务提供者、服务消费者按照Dubbo的设计执行相关的功能。 所谓的执行相关功能如下: 注册中心启动,监听消息提
    2021-04-05