Dubbo服务发布的几点心得

Dubbo服务发布(服务暴露)是Dubbo框架启动过程中服务初始化、启动本地监听、注册服务信息的

过程,是Dubbo对外实现可用性的基础!

        本篇主要本着涵盖全面、核心突出的原则去分析一下Dubbo的服务发布过程,以使自己对框架的

理解更为透彻! 分析的几个维度如下:

     1)Dubbo服务的xml配置是如何解析的

     2)发布流程是何时触发的

     3)统一数据模型URL是如何发挥作用的

     4)拦截器

     5)服务的本地暴露(服务监听初始化)和服务信息的注册

     6)Dubbo服务暴露的全流程对Dubbo应用场景的启示

    进入正题,

一、Dubbo服务的xml配置是如何解析的

       Dubbo框架利用spring对标签拓展的支持,定义了一些列自定义标签,以<dubbo:xx>开头, 然后

定义自己的标签处理器和对应的标签类,先简单讲一下spring标签拓展的规则:

      (a) 定义自定义标签的命名空间处理器,  例如在Dubbo就是 DubboNamespaceHandler

      (b) 定以特定标签的解析器类和标签属性描述文件*.xsd ,如Dubbo中的<dubbo:service> 中的解析器DubboBeanDefinitionParser 和 dubbo.xsd

      (c) 定义命名空间与处理器关联文件 spring.handlers (这个是spring约定的文件名,不能改,spring在启动时会自动加载该文件)

      (d) 定义命名空间的*.xsd文件的引用路径文件 spring.schemas (同上)

     通过自定义自己的一套标签,dubbo将服务类纳入spring容器的管理中,具体的解析可以看源码,如下:

     (1) 处理器定义

      

   (2) 解析器分析 DubboBeanDefinitionParser

   

    

      通过以上分析基本上了解了dubbo如何将自己需要关注的xml配置bean纳入到spring管理中去了


二、Dubbo的服务发布如何触发的

      当<dubbo:service>被spring解析和加入容器的时候,Dubbo框架对应的标签类实现了一系列

的spring生命周期事件接口,从而参与到spring容器创建过程中去,看源码如下:


dubbo在spring加载刷新bean的时候,会监听该事件通知,从而触发整个发布流程,源码如图:

 

  小拓展 : 我们知道Dubbo服务发布的时候支持延时暴露,譬如这样配置 <dubbo:service ...  delay="10" >,Dubbo框架是如何实现的,

其实没有什么高超技巧,开启一个延时线程,如图

  

三、统一数据模型URL

        URL是Dubbo自定义的参数传递的模型类,与Dubbo的拓展加载机制相配合,实现了类之间的无缝适配,

参数信息集中管理,是Dubbo各层之间的数据传递封装, 简单举几个例子,以便于理解!  

       loadRegistries(boolean provider) 方法负责抽离各个Dubbo的配置组件的信息、版本号、时间戳、线程id等, 然后组

装成URL,一个以zookeeper为注册中心的url实例如下:

       registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&organization=dubbox&owner=programmer&pid=3772&registry=zookeeper&timestamp=1493689627979

这里还有一非常重要的字段没有列举出来 就是protocol,为甚说他重要呢,看几个实例 :

ServiceConfig.java类中有一本地方法 doExportUrlsFor1Protocol,有一段代码负责本地代理构建和服务发布,


(1) 如下,是代理工厂适配类的结构

com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adpative implements com.alibaba.dubbo.rpc.ProxyFactory {
public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws java.lang.Object {
if (arg2 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg2;
String extName = url.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getInvoker(arg0, arg1, arg2);
}
public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0);
}
}

哈哈是不是有种恍然醒悟的感觉,没错,类似这种适配,就是通过统一数据模型url中的特定业务标志参数适配的

(2)如下,协议适配类结构这样的

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
public void destroy() {throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
}

通过以上的分析我们可以看出Dubbo的URL实际上为整个框架提供了统一的数据模型信息集中管理类自动适配。

这里有个非常重要的概念要引入一下,Invoker(代理执行器,这个是我杜撰的,至于为什么这样叫,待会讲 )

Invoker在Dubbo中实际上是一个抽象接口,许多协议实现了该接口,诸如DubboInvoker 等,单就服务发布而言,

该对象创建过程是这样的,本地服务类->装饰类->创建Invoker的抽象实例AbstractProxyInvoker

源码这样的,


   

  本地服务类的包装类Wrapper0,其结构如下(仅是部分代码):

  public classWrapper0extendsWrapperimplementsDC{

  public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException{ 
       
 com.alibaba.dubbo.demo.bid.BidService w; 
  try{ 
 
 w = ((com.alibaba.dubbo.demo.bid.BidService)$1); 
  }catch(Throwable e){ 
      throw new IllegalArgumentException(e); 
  } 
     
    try{ 
if( "bid".equals( $2 )  &&  $3.length == 1 ){  
return ($w)w.bid((com.alibaba.dubbo.demo.bid.BidRequest)$4[0]);

if( "throwNPE".equals( $2 )  &&  $3.length == 0 ) { 
w.throwNPE(); 
return null; 

   } catch(Throwable e) {      
throw new java.lang.reflect.InvocationTargetException(e);  

throw new com.alibaba.dubbo.common.bytecode.NoSuchMethodException("Not found method \""+$2+"\" in class com.alibaba.dubbo.demo.bid.BidService."); 
}

  }

显然,装饰类的invokeMethod方法包装了对本地实际服务类的方法的调用,结合上面了分析,Invoker实际上代理了对本服务的

执行,因此称之为代理执行器大笑

四、拦截器

       Dubbo的拦截器服务是服务调用时,执行Invoker过程中的功能增强,其实时序图如下:

       

 有没有眼前一亮,这不就是spring中的aop拦截器链嘛,没错原理是一样的,所以技术思想可以超越具体的架构,而去解决通用场景的问题

 Dubbo在执行export过程中,会去构建关联Invoker的调用Filter链,我们回到源码中继续分析,如图

其中的protocol是 Protocol$Adpative类型的, 通过Invoker携带的URL对象实现了方法的适配,最终实际上执行的是RegistryProtocol.export方法(因为

url中的protocol="registry"), OK,接下来会依次执行该类的装饰类ProtocolFilterWrapper、ProtocolListenerWrapper类中的export方法,如图所示


有点小复杂,不过我可以简单解释一下,上节谈到Dubbo的拓展点加载机制会用特定接口类型的装饰类对应的类进行装饰,譬如上述所讲的Protocol类型的

实现类, 当执行完上述的RegistryProtocol的装饰类方法后, 会进入RegistryProtocol类中export执行,如图


在doLocalExport 方法中执行 protocol.export()时, (该protocol根据前述的适配规则,是DubboProtocol类型), 所以又顺次进入了装饰类ProtocolFilterWrapper、ProtocolListenerWrapper类中的export方法中, 接下来看看ProtocolFilterWrapper装饰类具体实现细节:


经过漫长的前夜,终于迎来了拦截器链的黎明了,分析 buildInvokerChain 方法


说的直接点就是每一个Invoker都包含了对调用链上紧邻的下一个Invoker的引用, 在Invoker每一个调用方法invoke中都对应一个特定的拦截器

,它触发对下一个Invoker的调用,于是,整个拦截器链顺次执行,直到代理执行器Invoker执行完毕返回!


五、服务的本地暴露和服务信息注册

       服务的暴露过程实际上就是创建Exporter对象、将服务相关信息注册到注册中心对外发布的过程, 这里用时序图

来表示一下

                  

本地服务监听的初始化我会在Dubbo的RPC通信分析时单独去讲解,此处不再展开,重点讲一下服务信息注册

Dubbo抽象了注册中心服务的通用接口 Registry, 定义了基本的操作方法, 而通过RegistryFactory适配类和url获取

不同的注册中心实例。

六、Dubbo服务暴露的全流程对Dubbo应用场景的启示

    (1) 定义服务接口的粒度不要太细,控制接口的数量(dubbo是按服务接口发布的,每次都会消耗系统资源);

    (2) 控制服务接口方法的数量(否则代理类Wrapper中的invokeMethod会急剧膨胀);


好了,就啰嗦到这吧!


总重每一个坚持改变,让现状变得更好的人!