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®istry=zookeeper×tamp=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会急剧膨胀);
好了,就啰嗦到这吧!
总重每一个坚持改变,让现状变得更好的人!