Dubbo——服务端启动过程分析

服务端启动过程分析

1  触发服务注册过程

服务端的启动过程中,对服务暴露由ServiceBean类开始处理逻辑,该类继承了<dubbo:service>标签对应的配置类ServiceConfig,还实现了一系列Spring接口用于参与Spring容器的启动以及bean的创建过程中去。ServiceBean类实现了InitializingBean接口的afterPropertiesSet方法。

对于注解方式注册服务,首先在在Spring回调AnnotationBean类的postProcessAfterInitialization方法时,触发调用ServiceBean. afterPropertiesSet ()方法,若服务设置为非延迟注册,则直接调用在ServiceConfig.export()方法触发服务注册过程。

对于XML方式注册服务,在spring的容器ApplicationConetext的启动过程refesh过程中最后第二步会预先初始化单例bean,在bean的初始化过程中会设置beanName,设置容器的appliactionContext并回调afterPropertiesSet方法,若此服务为延迟注册,则跳过此过程;在最后一步finishRefresh会触发ContextRefreshedEvent实际,而ServiceBean实现了ApplicationListener接口的onApplicationEvent方法监听此事件,从而在此方法被触发时调用export方法注册服务。

上述设置服务注册延迟的方法是配置<dubbo:service>标签的delay属性。


2 生成注册地址

AbstractInterfaceConfig.loadRegistries(boolean provider)方法中生成注册地址。

       1、检查xml中是否配置注册信息,若没有则从dubbo.properties中获取dubbo.registry.address的配置参数,构建RegistryConfig对象并赋值。

对于如下xml配置,也可以配置多个注册地址,用逗号分开。

Dubbo——服务端启动过程分析

生成的注册URL地址如下:


Dubbo——服务端启动过程分析

3 暴露服务

        服务暴露由ServiceConfig.export()方法处理,在该方法中首先获取配置参数exportdelay,参数export表示是否暴露此服务,参数delay表示是否延迟暴露;若exportfalse或者未配置,则不进行后续的暴露逻辑处理(doExport方法);若delay有值,则启动一个守护线程,在delay时间之后进行暴露业务逻辑处理(doExport方法)。doExport方法中,进行如下逻辑:

1)加载配置参数applicationmoduleregistrymonitorprotocol等标签模块数据;若module中配置了registrymonitor,而未单独配置这两个模块的数据,则以module中的registrymonitor配置参数为准。

2)检查标签ref的值是否为接口GenericService的实现,若是则直接初始化interfaceClass值为GenericService.class;否则若不是则以标签interface的值创建interfaceClass类,并且检查ref标签的类名是否为interfaceClass的实现类,若不是则直接报错。

3)若local标签值不为空,则初始化该标签值的class类;若stub标签值不为空,则初始化该标签值的stub类;这两个参数值是作为服务端在测试时的桩程序使用的。

4)在调用doExportUrls方法,首先调用loadRegistries方法(第3.2小节)获取所有注册协议,然后遍历所有的服务暴露协议protocol,调用ServiceConfig.doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs)方法把每个服务根据配置的服务协议protocol暴露处理并向所有注册协议中注册。

ServiceConfig.doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs)方法进行暴露服务逻辑的处理,具体逻辑如下:

1)根据协议构建暴露服务的统一数据模型URL,例如根据如下配置

Dubbo——服务端启动过程分析

生成的URL地址:

Dubbo——服务端启动过程分析

      其中,对于method标签中的retry标签值,若为false,则在URL参数中设置<metodName>.retries等于0;否则等于method标签中的retry标签值;对于<dubbo:argument>标签,则在URL中设置以<metodName><index>key的值。URL参数中的generic表示是否为接口GenericService的实现类。

2)根据标签属性scope的参数值判断是否暴露服务。若为none,则不暴露服务;若等于“remote”,则进行远程暴露;若等于“local”则进行本地暴露;若为其他则本地和远程均暴露服务。默认情况下是本地和远程均暴露。


3.1本地暴露服务

ServiceConfig.exportLocal(URL url)方法中对url进行本地暴露,首先将URL的协议更改为了“injvm”、IP更改为了本地端口,端口变更为0。主要有代理工厂创建Invoker代理、Protocol暴露Invoker从而生成Exporter两个处理逻辑。

1)代理工厂创建Invoker代理

  此处用到了简单工厂模式,ExtensionLoader调用ExtensionLoader. getExtensionLoader(ProxyFactory.class).getAdaptiveExtension()方法创建ProxyFactory实现类的适配器类实例ProxyFactory$Adpative(如下图),此过程的具体事项在《扩展点加载机制》中详细介绍。

调用getInvoker方法,根据URL中的proxy参数选择具体的代理工厂,默认选择JavassistProxyFactory代理工厂进行处理,因为在ProxyFactory命名的文件中存在一条记录类StubProxyFactoryWrapper,该类中有以ProxyFactory参数的构造函数,故该文件中其他类的实例均StubProxyFactoryWrapper封装。

StubProxyFactoryWrapper类的getProxy方法的作用是检查URL中是否存在stub或者local参数值,若存在则进行<serviceName>Stub<serviceName>Local的本地服务暴露。若不存在则直接返回JavassistProxyFactory代理对象实例。

Dubbo——服务端启动过程分析Dubbo——服务端启动过程分析

在JavassistProxyFactory.getInvoker方法中创建了AbstractProxyInvoker类的子类对象,并重写了doInvoke方法,在该方法中通过反射的方式调用具体的实现类也就是说在消费端发送的请求会最终到此类的该方法中进行具体业务层处理逻辑的调用。代码如下:

Dubbo——服务端启动过程分析

2)InjvmProtocol暴露Invoker

  此方法主要是调用protocl.export()方法完成, ExtensionLoader调用ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()方法创建Protocol实现类的适配器类实例Protocol$Adpative(如下图),该工厂类根据protocol参数值选择具体的protocol类来处理服务暴露的工作;对于本地暴露,protocol参数值为“injvm”,故选择InjvmProtocol类的export()方法进行服务暴露工作

Dubbo——服务端启动过程分析

      Protocol类被ProtocolFilterWrapperProtocolListenerWrapper封装。在这两个类中对协议为“registry”的Invoker不做任何处理,对其他协议则进行过滤器链和监听器链的创建。

      该本地暴露服务的协议为“injvm”,1)首先由ProtocolFilterWrapperInvoker创建过滤器链;过滤器列表包括两部分,第一是选择Filter.class扩展点中存在@Active注解并且group值等于“provider”的过滤器实例,第二是URL参数“service.filter”的类名(在< dubbo:service >标签的filter属性设置),调用getExtension方法获取实例对象2)然后由InjvmProtocol类初始化InjvmExporter对象,并保存Invoker对象;3)最后在ProtocolListenerWrapper类中被封装成了ListenerExporterWrapper对象并返回,在封装监听器链的过程中,监听器包括扩展点为InvokerListener.class的自动**的实例列表(即有@Active注解的类的实例)以及服务端设置的引用监听器类实例(由< dubbo:service >标签的listener属性设置,在URL中存为参数“service.listener”),保存所有监听器类并执行监听器类的exported方法。

      将InjvmProtocolexport(Invoker)方法中创建的Exporter对象存入ServiceConfig对象的exporters属性中。至此本地暴露过程完成。

      其中,ProtocolFilterWrapper类主要是对Invoker添加过滤器链,添加了如下过滤器:EchoFilterClassLoaderFilterGenericFilterContextFilterTraceFilterTimeoutFilterMonitorFilterExceptionFilterValidationFilter;具体的实现逻辑见《过滤器链的创建》。ProtocolListenerWrapper是对Exporter构建了监听器链,执行在服务暴露(exporter)的过程中监听器所提供的回调函数,Dubbo没有实现监听器,但提供了业务扩展的接口,业务可以实现ExporterListener接口或ExporterListenerAdapter抽象类来,并在xml<dubbo:service>标签listener属性中配置监听器的类名即可。


3.2远程暴露服务

遍历所有的注册地址,在所有注册中心上完成服务暴露工作。大致逻辑如下:

1、获取<dubbo:monitor>标签的监控地址信息,并作为monitor参数放入服务提供者的URL中;

2、将服务提供者的URL地址作为export参数的值放入注册地址URL中;

3、调用ProxyFactorygetInvoker方法对注册地址URL创建注册Invoker代理,具体处理逻辑与本地暴露服务的一致,在此不重复讲解。

4、调用代理工厂Protocol$Adpative根据注册地址URL中的协议选择具体的Protocol处理类,此处选择了RegistryProtocol.export()方法处理服务暴露的事情,其中export方法的入参为第3步创建的注册Invoker代理;大致逻辑如下:

  4.1、以第3步创建的Invoker代理作为参数调用doLocalExport方法启动服务端的服务容器,最终返回封装后的Exporter对象(ExporterChangeableWrapper),具体逻辑如下:

       1)用RegistryProtocol类的内部类InvokerDelegete封装注册Invoker代理对象,并将服务提供者的URL地址(注册地址URL中的export参数值)赋值给该内部类的URL变量;URL的协议为服务提供者与服务消费者间使用的协议,目前Dubbo版本中支持的协议有dubbomockinjvmrmihessianthriftmemcachedredisrest

       2)调用代理工厂Protocol$Adpativeexport()方法处理服务暴露的事情,在该方法中根据服务提供者URL中的协议选择具体协议的Protocol实现类,并调用该实现类的export()方法处理服务暴露的事情。此处的逻辑与3.2.1 本地暴露服务》InjvmProtocol.export的处理一致,首先为InvokerDelegete创建代理;然后根据服务方提供的协议选择具体的Protocol暴露Invoker,主要的任务是在服务端启动服务容器、配置端口的监听等,具体逻辑在《各协议暴露服务的逻辑》中详细讲解。以DubboProtocol协议为例,在export方法之后返回DubboExporter对象,并被封装成ListenerExporterWrapper对象。

       3) 具体protocol暴露Invoker之后,创建了Exporter 对象,并将此对象存入AbstractProtocol.exporterMap中。其中key的组成方式:serviceGroup/serviceName:serviceVersion:portserviceName是配置protocolcontextpath属性值+ dubbo:serviceinterface值,serviceGroup group属性值,serviceVersionversion属性值,若该属性值为空或者为“0.0.0”则在key中忽略此部分。从而可以看出在同一个协议下面是否为同一服务由上述属性决定。

     4)将第2步中返回的ListenerExporterWrapper对象再次用RegistryProtocol$ExporterChangeableWrapper类进行封装。此类为RegistryProtocol的内部类,无实质性业务逻辑。

     5)上一步的封装对象放入RegistryProtocol.bounds对象中,其中key值为服务提供者URL,此举是为了防止重复暴露,在暴露服务之前要判断在此bounds中是否已经存在,若存在则不在执行暴露逻辑。

4.2服务注册工作

    1)根据注册地址URL获取Registry实例,在动态生成的代理工厂RegistryFactory$Adpative中根据注册协议选择具体的RegistryFactory来创建相应的Registry实例,目前支持的注册协议有dubbomulticastzookeeperredis。具体的处理逻辑在《各注册协议的处理逻辑》中讲解。以zookeeper协议为例,返回ZookeeperRegistry实例,在初始化该实例时完成了与注册中心的建链,并对注册中心的状态设置了监听器。

    2)获取服务提供者提供服务的URL地址;

   3)将上述服务URL地址向注册中心进行注册。以zookeeper协议为例,此过程是调用ZookeeperClientcreate方法在zookeeper中创建一个节点树,将服务提供者的URL作为数据存入节点树的叶子节点中。具体的处理逻辑在《各注册协议的处理逻辑》中讲解。

4.3 配置监听器

     对服务提供者的configurators节点配置监听器。首先在服务提供者的节点树中创建configurators节点,然后配置监听器OverrideListener,若通过Dubbo管理系统为服务设置动态参数,则动态配置的参数放在configurators节点目录下,并通知服务端的OverrideListener监听器,根据动态参数重新生成服务提供者URL,若URL有变化则重新暴露服务。

4.4 4.1中生成的Exporter对象重新创建一个新的Exporter对象。该新对象重写getInvokerunexport方法,getInvoker方法仅仅是调用4.1中生成的Exporter对象的getInvoker方法;unexport方法中,首先调用4.1中生成的Exporter对象的unexport方法,然后调用Registry实例的unregister方法,再从OverrideListener监听器中去除该URL地址,并取消订阅。

5、将第4.4步中返回的Exporter对象存入ServiceConfig类的exporters属性中。至此远程暴露过程完成

暴露服务的活动图

Dubbo——服务端启动过程分析