Dubbo源码分流程析

原文:http://humn-chou.iteye.com/blog/1866272

也希望加入我们QQ群一起交流      IT互联网技术交流 368614849


这是本人对于Dubbo源码分析的系列一,没有说明Dubbo是什么,不清楚请先了解,此处只是为了给自己做个笔记,也给正在学习Dubbo的同学一些借鉴,后期会继续奉上所有关于Dubbo的逻辑分析,包括Dubbo简介、初始化与请求细节、注册中心、监控中心、治理中心等(由于Dubbo本身的文档已经非常详细了,这里只是重构)。 

A. Dubbo初始化、请求处理过程:(此处不涉及非常细节处,均以dubbo协议为例) 
一、 Dubbo提供者初始化过程分析: 
a) Dubbo解析xml,初始化且暴露所有服务 
b) 由服务配置类ServiceConfig进行初始化工作及服务暴露入口 
i. ServiceConfig.export(); 
c) 服务可以多协议暴露 
i. ServiceConfig.doExportUrls(); 
d) 在某个协议下暴露服务 
i. ServiceConfig.doExportUrlsFor1Protocol(); 
e) 将服务实现类转化成invoker 
i. proxyFactory.getInvoker(service,interface,url); 
ii. 提供者端的invoker封装了服务实现类、URL、Type,状态均是只读,线程安全,是Dubbo中核心模型,Dubbo中的是实体域,其他所有模型都向它靠拢或转化成它,是一个可执行体,通过发起invoke来具体调用服务类,它可能是个本地实现类,也可能是个远程实现类,也可能是个集群实现invoker,由ProxyFactory产生,具体是AbstractProxyInvoker 
f) 暴露封装服务的Invoker 
i. Protocol.export(invoker); 
ii. Protocol是Dubbo中的服务域,只在服务启用是加载且无状态,天生线程安全,它是实体域Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理,是Dubbo中远程服务调用层。Dubbo有多种协议支持,有dubbo、http、rmi、hessian、injvm、webService等,下面具体说明服务暴露的细节,包括服务的注册、订阅、暴露、过滤器、监听器的初始化,其中服务的暴露则为Dubbo中的一重点。 
g) 注册中心协议集成,装饰真正暴露引用服务的协议,增强注册发布功能 
i. RegistryProtocol.export(invoker); 
ii. ServiceConfig中的protocol是被多层装饰的Protocol,是DubboProtocolRegistryProtocolProtocolListenerWrapperProtocolFilterWrapper(此处是以dubbo协议为例,还有HttpPtotocol、HessianProtocol、RmiProtocol),其中ProtocolFilterWrapper是负责初始化invoker所有的Filter、ProtocolListenerWrapper是负责初始化暴露或引用服务的监听器、RegistryProtocol是负责注册服务到注册中心与向注册中心订阅服务、DubboProtocol是负责服务的具体暴露与引用,且DubboProtocol也负责网络传输层、信息交换层的初始化及底层NIO框架的初始化 
h) 注册中心协议处理invoker后交给具体协议进行服务暴露 
i. RegistryProtocol.doLocalExport(invoker) 
ii. RegistryProtocol对原始invoker做一些简单状态后交给具体协议暴露,协议不关系invoker如何生成,由上层传入。接下来就是协议对invoker的暴露,此步骤是Dubbo中的核心。 
i) 协议层暴露服务前初始化过滤器与服务暴露引用的监听器 
i. 此步骤由上面提到的ProtocolListenerWrapper、ProtocolFilterWrapper完成,它们是协议的装饰者,增强功能(装饰模式大量使用在Dubbo中)。 
j) 协议暴露服务 
ii. DubboProtocol.export(); 
iii. 进入远程调用层Protocol,此层封装了所有remote层逻辑,对上层透明,暴露服务大致可以分为两步,第一是将invoker转化成Exporter;第二是根据URL绑定IP与端口建立NIO框架的Server;此两步的联系是通过URL关联在一起,协议层缓存了所有暴露的服务,其中key是由group、serviceName、version、port组成,它们的组合具体一个暴露服务,当NIO客户端发起远程调用时,NIO服务端通过此key来决定具体调用执行哪个Exporter,即执行的Invoker,对于NIO框架的底层通信下面会有讲解。 
j) 协议层创建Exporter后创建NIO Server完成服务的暴露 
i. DubboProtocol.openServer(url); 
ii. 创建封装了Inoker,key的DubboExporter后,通过URL创建NIO Server,且缓存暴露的Exporter,防止重复暴露。另外,同一个JVM中相同协议的服务共享同一个Server,不同服务中只有accept、idleTimeout、threads、heartbeat参数的变化会引用Server中属性的变化,但同JVM中同协议的服务均是引用同一个Server,第一个服务暴露是创建Server,其他服务的暴露是Server最多只是重置个别参数。 
 

k) 协议层创建NIO Server,并缓存Server 
i. DubboProtocol.createServer(url); 
ii. Dubbo底层通信是通过支持异步、事件驱动的NIO网络编程框架,如:Netty、Mina、Grizzly,此框架是典型的Reactor模式使用,使得单个线程处理多个请求,且支持多请求并行执行,NIO接受请求处理流程是读取请求数据解码执行业务逻辑编码发送回应消息,Dubbo是对NIO框架的再次抽象封装,加入一些Dubbo需要的逻辑,通过抽象扩展Handler完成,如HeaderExchangerHandler完成请求-响应模式、同步转异步模式消息发送,AllChannelHandler通过线程池完成请求、响应、连接等并行执行,下面会详细介绍。 
iii. Exchangers.bind(url,requestHanler); 
iv. Exchangers是门面类,Exchanger的逻辑封装在里面,通过此FACADE调用Exchanger逻辑,Dubbo目前只有一个HeaderExanger实现。从协议层进入Exchanger标志着程序进入了remote层,此层有消息交换层、网络传输层。当协议层调用bind(url,requestHanler)方法并传入最原始的处理Handler时,接下来就是remote层的Server和Hanlder及其他参数初始化的过程。 
v. HeaderExchanger.bind(url,requestHandler); 

vi. 此处是消息交换层与网络传输层初始化的入口,且负责将初始化后的Server传回给协议层。处理流程大致是Dubbo一系列Handler初始化、Server初始化。Handler包装过程是 协议层传入的原始requestHandlerHeaderExchangerHandlerDecodeHandlerAllChannelHandlerHeartbeatHandlerMultiMessageHandlerNettyServer[NettyHandler、NettyCodecAdapter]HeaderExchanerServer,HeaderExchanerServer封装了最终的Server,返回给上层协议层,NettyHandler、NettyCodecAdapter是NIO框架的直接处理Handler,NIO框架接受到消息后,先由NettyCodecAdapter解码,再由NettyHandler处理具体业务逻辑,再由NettyCodecAdapter编码后发送,NettyServer是个很重要的类,它既是Server又是Handler,而HeaderExchangerServer只是Server,所有NettyServer参与的Handler的处理过程,MultiMessageHandler是多消息处理Handler、HeartbeatHandler是处理心跳事件的Handler、AllChannelHandler是消息派发器,负责将请求放入线程池,并行执行请求,且Dubbo有多种线程模型、DecodeHandler是编码解码Handler、HeaderExchangerHandler是信息交换Handler,将请求转化成请求-响应模式与同步转异步模式,地位非常重要、requestHandler最后执行的Handler,它会在协议层选择Excporter后选择Invoker,进而执行Filter与invoker,最终执行请求服务实现类方法。 
其中NIO框架的通信管道Channel也被Dubbo封装了,Channel直接触发事件并执行Handler,有ChannelNettyChannelHeaderExchangerChannel,通信管道Channel在有客户端连接Server时触发创建并封装成NettyChannel,再由HeaderExchangerHandler创建HeaderExchangerChannel,负责请求-响应模式的处理,注意NettyChannel其实也是个Handler,而HeaderExchangerChannel只是个Channel,是Handler的类说明它也具体Handler特性。 
另外,还初始化一些参数,有AllChannelHandler的线程池模型、NettyServer的codesc解码编码方式,timeout超时时间,connectTimeout连接超时时时间,accepts提供者最大接受连接数设置,ildeTimeout时间设置,此处的参数得到都是为了后期可能重置准备。还有消息的序列化与发序列化工作全在NettyCodecAdapter中发起完成。 
至此,分析了Dubbo初始化remote层所有初始化流程,还有,对于Transporters也是个门面类,内部调用网络传输层的逻辑,如:NettyTransporter、MinaTransporter。 
附上连接过程:(当有客户端连接Server时) 
0.NettyHandler.connected() 
1.NettyServer.connected() 
2.MultiMessageHandler.connected() 
3.HeartbeatHandler.connected() 
4.AllChannelHandler.connected() 
5.DecodeHandler.connected() 
6.HaderExchangeHandler.connected() 
7.requestHandler.connected() 
8.执行服务的onconnect事件的监听方法 
下面是Handler、Server、Channel的结构图: 



l) 将初始化后的Server返回协议层 
i. 创建好的Server返回,此Server对象很重,缓存 
m) 协议层将暴露后的Exporter返回给注册中心协议层,最后返回给ServiceConfig进行缓存 
i. 协议层成功返回Exporter给注册中心协议层,标志着服务暴露完毕,接下来是与注册中心的操作,逻辑上看,注册中心负责服务的注册与发现,所有提供者向注册中心注册服务,并订阅重载配置,所有消费者向注册中心注册服务,并订阅服务,消费者通过注册中心自动发现所有服务提供者,且本地缓存提供者类表,注册中心宕机也不影响消费者调用;程序上看,注册中心也是普通的RPC服务,所有消费者、提供者与注册中心都是长连接。Dubbo目前注册中心服务有MulticastRegistry、ZookeeperRegistry等,关于注册中心的详细介绍待后期奉上。 
ii. RegistryProtocol.getRegistry(); 
iii. 得到具体注册中心服务且连接注册中心,此时提供者作为消费者引用注册中心核心服务RegistryService 
iv. Registry.register(url); 
v. 调用远端注册中心的register()方法进行服务注册,且若有消费者订阅此服务,则推送消息让消费者引用此服务,注册中心缓存了所有提供者注册的服务以供消费者发现。 
vi. Registry.subscribe(url,listener); 
vii. 提供者向注册中心订阅所有注册服务的覆盖配置,当注册中心有此服务的覆盖配置注册进来时,推送消息给提供者,让它重新暴露服务,这由管理页面完成。 
viii. 返回暴露后的Exporter给上层ServiceConfig进行缓存,便于后期撤销暴露。 
ix. 总结:Exporter的创建、Server的创建、服务的注册与订阅,这些逻辑都是分离的,它们是通过URL联系在一起的。一般情况下,同JVM同协议下的服务共享同一个Server,且消费端的引用这些服务的也可共享一个Client,从而实现多个服务共享同一个通道进行通信,且是基于长连接下,减少了通信的握手次数,高效率通信,另外,远程调用层与信息交换层及网络传输层是Dubbo的核心。 
二、 Dubbo消费者初始化过程分析: 
a) Dubbo解析xml,初始化所有注入的引用服务及init=true引用服务 
b) 所有dubbo引用服务均由ReferenceConfig入口进行服务的引用 
i. ReferenceConfig.get(); ReferenceConfig.init(); 
ii. 消费者最终的得到的是服务的代理,在创建代理前初始化所有配置信息,此过程是线程安全的。引用服务的大致过程是由远程调用创建消费者端Invoker,再由ReferenceConfig创建代理返回,此过程的核心是创建消费端的Invoker。在引用服务前,ReferenceConfig先需要判断是否是引用本地服务injvm、是否是点对点直连、是否是通过注册中心连接,判断之后进行最重要的一步:服务的引用。 
c) 进入远程调用层进行服务的引用 
i. Protocol.refer(interface,url); 
ii. 此方法返回的是个集群invoker,若有多个提供者提供服务,Dubbo将多个服务Invoker伪装成一个集群Invoker,且这个集群Invoker内部的多个invoker,由Directory完成,Directory与List类似,但由不同,当提供者推送消息过来后,Directory可以动态变化,还可以通过Router路由提供者及LoadBalance根据负载均衡算法选中一个提供者,所以当上层进行Invoker调用时,会是:Cluster.invoke()Directory.list()Router.route()LoadBalance.select()invoke.invoke();另外,在invoker转成proxy时,Dubbo对代理也有装饰,如Stub,这个可以实现在真正调用invoker代理前可以做一些事情,类似AOP功能。上面是大致说明以下消费者引用服务的过程,下面详细说明。 
d) 首先进入注册中心远程调用层RegistryProtocol,且它也经过了ProtocolFilterWrapper、ProtocolListenerWrapper装饰,功能增强 
i. RegistryProtocol.refer(); 
ii. 同提供者相同,消费者也首先需要连接注册中心,即引用注册中心这个特殊的RPC服务,并通过注册中心进行服务的订阅与注册。然后判断引用是否是注册中心RegistryService服务,若是绕过注册中心与集群等直接返回刚得到注册中心服务即可,若不是,则说明是普通服务,则需要进入注册中心与集群下面的逻辑。在选择集群策略前,先需要判断引用服务是否需要合并不同实现的返回结果,及是否配置group="*" merger="true" (配置详细说明再说),若配置了,则选择默认的分组聚合集群策略,若没配置,则选择配置的集群策略cluster="failback"或默认策略。下面进入服务引用的重点逻辑分析。 
e) 服务引用,且向上层返回 
i. RegistryProtocol.doRefer(cluster,registry,type,url); 
ii. 消费端引用服务主要逻辑部分,服务引用大致逻辑是组合url、type创建目录类Directory,此类非常重要,它的功能很复杂,可以看成是NotifyListener服务通知监听器、可以看成Protocol、Registry的聚合类、可以看成一个消费端的List,但它又不同于List,他可以随着注册中心的消息推送而动态变化服务的Invoker数,时刻监听着提供者的变化,典型观察者模式的使用,它封装了所有服务真正引用逻辑、覆盖配置、路由规则等逻辑,初始化时只需要向注册中心发起订阅请求,其他逻辑均是异步的处理,包括服务引用等,且对上层Cluster层是透明的,ResitryProtocol将多个Invoker伪装成一个Invoker返回给上层来转化成代理,上层甚至不知道这是个集群Invoker,在伪装成一个Invoker时,Cluster也被装饰了,增强了Mock功能,这个详细暂不在此处说明。顶层得到伪装后的Invoker,当调用时,会根据初始化时的集群策略、负载均衡策略、路由策略进行选择调用某个Invoker。下面看看Directory的初始化与服务异步引用的分析及集群的处理步骤。 
f) 目录类Directory初始化、服务订阅、集群伪装 
i. 在RegistryProtocol中会根据url、type、protocol、registry初始化Directory,Directory功能非常强大,此处暂不说明详细功能。订阅服务前,注册中心首先注册服务,便于后期的监控中心提取数据,通过Directory订阅服务directory.subscribe(url),当有服务提供时,注册中心会推送服务消息给消费者,消费者再进行服务的引用,此处暂不详细说明注册中心的处理逻辑。下面是Directory中的代码块 
,接下来服务的引用与变更全部由Directory异步完成,集群策略会将Directory伪装成一个invoker, cluster.join(directory),此代码就是伪装逻辑,Dubbo有很多集群策略,可以配置切换,具体集群逻辑暂不属于这里,后期说明。RegistryProtocol再将伪装后的Invoker返回给上层ReferenceConfig用来创建代理,所以消费者端的对象引用的远程服务其实引用的都是个代理,真正逻辑只有在调用时触发,包括Stub、Proxy、Mock、Cluster、Router、LoadBalance、Filter、Invoker、NIO框架底层通信。 
g) Directory服务引用、NIO框架初始化 
i. 注册中心接收到消费端发送的订阅请求后,会根据提供者注册服务的列表,推送服务消息给消费者,此处的逻辑不同的注册中心实现逻辑不通,详细暂不说明,消费者端接受到注册中心发来的提供者列表后,进行服务的引用,触发Directory监听器的可以是订阅请求、覆盖策略消息、路由策略消息(后两者由管理页面设置),接下面就是服务的引用过程,这逻辑都隐藏在Directory中。 
invoker = new InvokerDelegete<T>(protocol.refer(serviceType, url), url, providerUrl); 
h) 真正进入远程调用层,进行服务的引用与NIO框架的初始化 
i. DubboProtocol.refer(type,url); 
ii. 同服务的暴露逻辑相同,服务的引用步骤也分为两步,一是消费端Invoker的创建,如DubboInvoker,而是NIO框架的Client创建,这两步骤虽然是也是分离的,但它们不是通过URL联系在一起的,而是Invoker聚合了Client,当调用Invoker时,底层实际上是调用Client逻辑进行底层通信。 
iii. new DubboInvoker<T>(serviceType, url, getClients(url), invokers); 
iv. Invoker创建,可以看出Invoker聚合了Client,此Invoker对象很重,远程调用层进行了缓存invokers.add(invoker);。 
v. getClients(url) 
vi. 此代码负责NIO框架Client创建及初始化,和提供者初始化相似,消费者的初始化也基本上围绕着Client、Handler、Channel对象的创建与不断装饰的过程,不同的是消费者底层是与提供者Server建立连接,此过程在remote层下完成,remote层可分为消息交换层与网路传输层即Exchanger与Transporter层,还有,我们在说提供者初始化时,说过同个JVM中相同协议的服务共享一个Server,同样在消费者初始化时,引用同一个提供者的所有服务可以共享一个Client进行通信,这也就实现了Server-Client在同一个通道中进行通信,实现长连接的高效通信,但是在服务请求数据量比较大时或请求数比较多时,可以设置每服务每连接或每服务多连接可以提高通信效率,具体是通过消费者方connections=2设置连接数。所有消费者端Client有两种,一种是共享型Client,一种是创建型Client,当然共享型Client属于创建型Client一部分,下面具体说说这两种Client创建的细节,也是服务引用的重要细节。 
 

vii. getSharedClient(url) 
viii. 此为创建共享型Client,共享型Client是指消费者引用同一提供者的服务时,使用同一个Client来提高通信效率,所以对于消费者来说,它连接的每个提供者都需要创建一个创建型Client,其他来自相同提供者的服务引用即可共享此Client,关于创建型Client的创建下面再说。对于共享型Client虽然可以提高通信效率,但会带来一个问题,就是如果所有共享一个Client的服务中的某个服务close了Client会导致其他服务都不能继续通信,这是个安全性问题,Dubbo的解决方案是通过计数的方式来控制这个安全问题,并在共享Client真正关闭后创建一个懒加载的幽灵Client,以备特殊情况使用(此处不说非常细节)。 
 

ix. initClient(url) 
x. 此为创建创建型Client,也是消费者端服务引用最核心的地方,封装了服务引用中remote层初始化的所有逻辑,与提供者端类似,就是Client、Handler、Channel的创建与不断装饰的过程,在说Client创建细节前,先说说Client类型,与其他产品一样,Dubbo中也有懒加载的概念,即Client分为正常马上连接的Client与懒加载的Client,懒加载的Client是个Client代理,当消费者真正调用服务时,才会去初始化Client、Handler、Channel,也就是才会与Server建立连接。 
xi. Client、Handler、Channel初始化 
xii. 此步骤是服务引用的核心的核心,逻辑完全封装在Protocol远程调用层,对于上层是完全透明的。在进行创建前,Dubbo默认开启remote层的心跳机制与禁止BIO通信,心跳机制是提供者与消费者之间的心跳通信,主要是防止通信异常,如提供者宕机,消费者自动重连提供者。下面重点是服务引用即Client的创建,此过程完成由Directory发起,有关于Directory的细节,后期会专栏介绍此类。同提供者类似,服务引用无非也就是初始化信息交换层与网络传输层即Exchanger与Transporter,且分别由门面类Exchangers与Transporters发起,Transporter返回的Client再经过Exchanger封装装饰后完成初始化并返回给上层Protocol。 


详细是:远程调用层Protocol传入原始Handler,后续将是此Handler不断装饰的过程,下面是Handler与Server初始化过程:requestHandlerHeaderExchangerHandlerDecodeHandlerAllChannelHandlerHeartbeatHandlerMultiMessageHandlerNettyClient[NettyHandler、NettyCodecAdapter]HeaderExchanerClient 此处remote最终返回包装了NettyClient的HeaderExchangerClient到Protocol层,与提供者相比,消费者有以下异同: 
1. 初始化的大致逻辑相同 
2. 提供者暴露服务是绑定IP,而消费者是与提供者建立连接 
3. 与NettyServer相同,NettyClient也是个Handler 
4. 与HeaderExchangerServer不同,HeaderExchangerClient也是个Channel,这点一定注意 
其中各个Handler、Client与提供者那里基本类似,这里不重复说明它们的功能,对于底层无非就是NIO框架的初始化,与Server建立连接。前面也说过了,返回到protocol层的Client被封装成消费端的Invoker,再返回给重要的Directory,那里动态引用了该服务所有与提供者建立后的Invoker,而真正protocol返回给上层的Invoker是封转了Directory的集群伪装Invoker,当真正调用时,才会根据集群策略、路由策略、负载均衡策略从Directory选择一个Invoker进行调用即Client与Server进行通信,所以Directory在Dubbo占有非常重要的位置,它的功能很复杂。 
下面再说说消费者发起的Channel的创建: 
消费者的NIO框架初始化是有一步很重要,就是与提供者建立连接即Channel的创建,NIO框架采用的双向通讯的通道Channel,而Dubbo对Channel也做了封装,封装过程是ChannelNettyChannelHeaderExchangerChannel,还有一点非常重要,查看Dubbo源码可知,NettyChannel其实也是个Handler,而HeaderExchangerChannel只是个Channel。在消费者端发起连接时,提供者端与消费者端的NettyHandler将被触发,将执行一系列的Handler或Server逻辑,在NettyHandler触发连接时,执行channelConnected创建Channel,Dubbo对NIO的Channel进行了包装,实现创建NettyChannel,它封装了NIO的Channel与包装Handler的NettyClient,值得注意的所有的事件都由NIO的Channel触发,如:写事件、读事件、连接事件、断开连接事件、出现异常事件。在Handler执行到HeaderExchangerHandler时,创建HeaderExchangerChannel,并且封装从Handler创建并传下来的NettyChannel。其中HeaderExchangerClient构造中有下面这段代码: 

HeaderExchangerClient也有个封装了NettyClient的HeaderExchangerChannel,注意这个,因为这是消费者发起请求是底层通信的入口。 
下面给出Client与Channel结构图,Handler图上面已有: 


总结:所有的初始化,都是为了消费者与提供者之间的通信做准备,下面请结合初始化过程,重点查看通信的过程。再说说,这里的服务引用过程全部隐藏在Directory中,对上层完全透明,只有注册中心触发Directory进而触发服务引用或变更,而上层来说,它接受到的是集群伪装后的Invoker,然后再将Invoker转成Proxy,所以Spring中引用的是一个代理,只有消费者发起调用时,才有一系列的集群、路由、负载均衡逻辑,主要调用逻辑请看下面。 
补充:和提供者暴露服务有暴露监听器一样ExporterListener,消费者端有引用服务的监听器InvokerListener,在服务引用时执行。 
三、 Dubbo消费者--提供者请求过程与响应过程: 
a) 大致过程 
i. 消费者发送请求提供者处理消费者接受请求 
ii. 在提供者与消费者端都初始化完毕时,提供者对外暴露的是Expoter、消费者对外提供的是Proxy,接下来就是从Proxy的执行到Exporter执行的过程,其中Dubbo中的会话域Invocation承载着提供者与消费者端的消息传递,并在每个线程栈中使用。下面是大致处理逻辑: 
----------------------------------1.消费者发起请求------------------------------------------- 
1. testService.hello();//消费者端呈现层引用的远程服务,这其实就是个代理 
2. testServiceSub.hello();//proxy代理被Sub包装了,增强Sub功能,类似AOP 
3. testServiceProxy.hello();//消费者端Invoker代理 
4. InvokerInvocationHandler.invoker();//由代理类执行的invocationHandler 
5. MockClusterInvoker.invoke();//集群对集群Invoker进行的包装,Mock功能 
6. ClusterInvoker.invoke();//执行集群伪装的Invoker 
7. Directory.list();//查找伪装在集群后面的所有Invoker 
8. Router.route();//通过路由规则策略从list中选择一些Invoker 
9. LoadBalance.select();//通过负载均衡选策略选中一个Invoker执行 
10. Filter.invoke();//执行所有消费者端Filter 
11. Invoker.invoke();//执行消费者端的Invoker即DubboInvoker 
12. HeaderExchangerClient.request();//信息交换Client执行请求,其中封装了NettyClient、HeaderExchangerChannel 
13. HeaderExchangerChannel.request();//此类封装了NettyClient,信息交换通道创建Request执行请求,并创建DefaultFuture返回 
14. NettyClient父类AbstractClient.send();//消费者端Client执行请求 
15. NettyChannel.send();//Netty通道执行请求发送 
16. Channel.write();//NIO框架通知执行写操作,并触发Handler 
17. NettyHanlder.writeRequested();//执行NIO框架的顶级Handler 
18. NettyCodecAdapter.encode();//执行NIO框架的编码逻辑,以下执行Handler 
19. NettyClient父类AbstractPeer.send();//执行装饰了Handler的NettyCient 
20. MultiMessageHandler.send();//多消息处理Handler 
21. HeartbeatHandler.send();//心跳处理Handler 
22. AllChannelHandler.send();//消息派发器Handler 
23. DecodeHandler.send();//编码Handler 
24. HeaderExchangerHandler.send();//信息交换Handler 
25. requestHandler父类ChannelHandlerAdapter.send();//最后执行Handler,以上Handler的执行在消费者端是没有意义,因为Channel.write ()执行时,提供者端已经收到消息了,也正在处理了,详细逻辑后面再说,这里为了完整性才这样写(以上是消费者请求大致过程,下面是提供者接受请求后处理并响应消费者过程)。 
------------------------------2.提供者处理并响应请求-------------------------------------- 
26. NettyCodecAdapter.messageReceived();//提供者通过NIO框架通信并接受到消费者发送的信息,当然,这里屏蔽了所有NIO框架的逻辑,也屏蔽了所有编码解码与序列化反序列化的逻辑 
27. NettyHandler.messageReceived();//提供者端的NIO顶级Handler处理 
28. NettyServer.received();//NIO框架的Server接受请求信息 
29. MultiMessageHandler.received ();//多消息处理Handler 
30. HeartbeatHandler.received ();//心跳处理Handler 
31. AllChannelHandler.received ();//消息派发器Handler 
32. DecodeHandler.received ();//编码Handler 
33. HeaderExchangerHandler.received ();//信息交换Handler,请求-响应模式 
34. requestHandler.reply();//执行与Exporter交接的最初Handler 
35. getInvoker();//先得到提供者端的Exporter再得到相应的提供者端Invoker 
36. Filter.invoke();//执行所有提供者端的Filter,所有附加逻辑均由此完成 
37. AbstractInvoker.invoke();//执行封装了服务实现类的原始Invoker 
38. testServiceImple.hello();//执行服务实现类逻辑 
39. NettyChannel.send();//HeaderExchangerHandler得到执行结果Response再返回给消费者,此代码由HeaderExchangerHandler发起 
40. Channel.write ();//NIO框架通知执行写操作,并触发Handler 
41. NettyHanlder.writeRequested();//执行NIO框架的顶级Handler 
42. NettyCodecAdapter.encode();//执行NIO框架的编码逻辑,以下执行Handler 
43. NettyServer父类AbstractPeer.send();//执行装饰了Handler的NettyServer 
44. MultiMessageHandler.send();//多消息处理Handler 
45. HeartbeatHandler.send();//心跳处理Handler 
46. AllChannelHandler.send();//消息派发器Handler 
47. DecodeHandler.send();//编码Handler 
48. HeaderExchangerHandler.send();//信息交换Handler 
49. requestHandler父类ChannelHandlerAdapter.send();//最后执行Handler,以上Handler的执行在提供者端也是是没有意义,因为Channel.write ()执行时,消费者端已经收到响应消息了,也正在处理了,详细逻辑后面再说,这里为了完整性才这样写(以上是消费者请求与提供者接受请求并响应大致过程,下面是消费者接受到提供者响应后处理过程)。 
----------------------------------3.消费者接受响应------------------------------------------ 
50. NettyCodecAdapter.messageReceived();//提供者通过NIO框架通信并接受提供者发送过来的响应 
51. NettyHandler.messageReceived();//消费者端的NIO顶级Handler处理 
52. NettyClient.received();//NIO框架的Client接受响应信息 
53. MultiMessageHandler.received ();//多消息处理Handler 
54. HeartbeatHandler.received ();//心跳处理Handler 
55. AllChannelHandler.received ();//消息派发器Handler 
56. DecodeHandler.received ();//编码Handler 
57. HeaderExchangerHandler.received ();//信息交换Handler,请求-响应模式 
58. DefaultFuture.received();//设置response到消费者请求的Future中,以供消费者通过DefaultFuture.get()取得提供者的响应,此为同步转异步重要一步,且请求超时也由DefaultFuture控制。 
-----------------------------------4.阻塞或异步断点--------------------------------------- 
59. Filter.invoke();//消费者异步得到响应后,DubboInvoker继续执行,从而Filter继续执行,从而返回结果给消费者。 
60. InvokerInvocationHandler.invoker();//执行invocationHandler后续代码 
61. testServiceProxy.hello();//消费者端Invoker代理后续代码 
62. testServiceSub.hello();//执行后续代码 
63. testService.hello();//执行后续代码 
64. 呈现结果给消费者客户端 
总结:消费者端的DubboInvoker发起请求后,后续的逻辑可以说是异步的或是指定超时时间内阻塞的,直到得到响应结果后,继续执行DubboInvoker中逻辑。对于异步请求时,消费者得到Future,其余逻辑均是异步的。以上主要核心逻辑,对于附加功能,大部分由Filter增强完成。消费者还可以通过设置async、sent、return来调整处理逻辑,async指异步还是同步请求,sent指是否等待请求消息发出即阻塞等待是否成功发出请求、return指是否忽略返回值即但方向通信,一般异步时使用以减少Future对象的创建和管理成本。



这是本人对于Dubbo源码分析的系列二,对于系列一,主要是对Dubbo的初始化、请求-响应过程的分析,并没有详细分析每个细节,接下来的系列主要是分析Dubbo中的各个细节,以供更加准确的了解这个支持SOA的RPC分布式框架,本系列介绍Dubbo中的编码解码与序列化反序列化逻辑及消费者、提供者、注册中心之间的连通性设计,当然,这里只是介绍,具体代码细节不会涉及。 

一、Dubbo中编码解码与序列化反序列化逻辑 
a) 序列化反序列化 
i. 顾名思义,序列化的过程就是将内存中对象格式化成可以存储、远程传输等的过程,而反序列化的过程恰好相反。Dubbo中序列化就是对象与流之间转化的过程,且提供了dubbo、hessian、java、json、nativejava多种序列化支持。如dubbo: 
public ObjectOutput serialize(URL url, OutputStream out) throws IOException { 
        return new GenericObjectOutput(out); 
    } 
public ObjectInput deserialize(URL url, InputStream is) throws IOException { 
        return new GenericObjectInput(is); 

序列化是RPC中不可缺少的一部分,也是Dubbo中数据传输的基础,是对编码解码功能的支持。 
b) 编码解码 
i. NIO中数据的传输是通过缓存器ByteBuffer进行读写,而Netty中使用的是ChannelBuffer缓存器,此处暂不详细说明此缓存器,因为这属于Dubbo底层逻辑。Dubbo在使用Netty进行通信时,没有直接使用ChannelBuffer数据结构,而是使用Request、Response这样我们认识的数据结构,由于Nio底层框架不认识此数据结构,所以我们需要对其进行编码与解码,这个过程可以说是缓存器ChannelBuffer与对象之间的转化,但注意的是,提供者与消费者之间进行传输的是RpcInvocation与Result,不同的是接受到的是DecodeableRpcInvocation与DecodeableRpcResult,这也就是DecodeHandler的作用,实现延迟解码。Request与Response都是在提供者端与消费者端根据具体数据进行创建的。主要有以下编码解码(注意编码解码的逻辑贯穿在请求-响应中): 
1. 消费者发起请求:消费者创建封装了RpcInvocation的Request,经过序列化编码后发送给提供者,步骤如下: 
InvokerInvocationHandler.invoker()、HeaderExchangerChannel()创建封装RpcInvocation的Request,再由NettyHanlder.writeRequested()、NettyCodecAdapter.encode()进行序列化编码后发送给提供者,此过程中消费者可以暴露一个提供者回调服务,也就是Callback,值得注意的是,此回调服务没有真正的开启Server绑定IP,而是只是export()了,再使用回调服务所在接口的通信通道Channel。 
2. 提供者接受请求:提供者接受ChannelBuffer后,对其进行反序列化再编码后生成消费者的Request,由NettyHanlder.messageReceived()、NettyCodecAdapter.decode()触发完成逻辑处理,此过程中提供者可以引用一个消费者提供的回调服务,也就是Callback。 
3. 提供者响应请求: 提供者处理消费者的请求后进行响应,即创建Response进行回应,也由NettyHanlder.writeRequested()、NettyCodecAdapter.encode()进行序列化编码后发送给消费者。 
4. 消费者接受响应:消费者接受ChannelBuffer后,对其进行反序列化再编码后生成提供者发来的Response,由NettyHanlder.messageReceived()、NettyCodecAdapter.decode()触发完成逻辑处理。 
总结:这块属于Dubbo底层通信逻辑,贯穿在请求-响应中,有必要了解下,且对于回调服务的暴露与引用都在这里处理,对于回调服务的了解非常重要。还有Dubbo在编码解码时,不是简单的发送的数据体,还发送了一些头信息,比如:Magic High、Magic Low、Serilazation Id、event、Two Way、Req/Res、response status、request id、data length. 
这个与HTTP PROTOCOL相似! 

二、Dubbo中消费者、提供者、注册中心之间的连通性设计 

a) 三者之间均是长连接,且提供者、消费者一般只在初始化时与注册中心交互或在服务变更时交互 
b) 消费者断开时,提供者会马上检测到断开事件,并且关闭与这个消费者之间的连接;注册中心也会马上检测到断开事件,并且马上与它断开连接,还有会通过ondisconnect方法进行清除这个消费者在注册中心注册与订阅的所有服务。 
c) 提供者断开时,消费者会马上检测到断开事件,并且关闭与这个提供者之间的连接, 会无限次重连提供者;注册中心也会马上检测到断开事件,并且马上与它断开连接,还有会通过ondisconnect方法进行清除这个提供者在注册中心注册与订阅的所有服务,且通知订阅服务的消费者进行重新对比引用服务列表。此时,消费者会无限次重连提供者、提供者无限次重复注册订阅服务及无线重连注册中心,或由心跳机制探测后进行重连注册中心(心跳机制是为了防止那些意外断线而消费者无法检测到FIN事件,从而无法触发重新连接等一系列操作,也不能感知注册中心是否真的存活)。 
d) 注册中心断开时,提供者与消费者会马上检测到断开事件,并且关闭与注册中心的连接,由心跳机制完成重连注册中心,提供者与消费者会收集所有注册的服务与所有订阅的服务到失败列表,进行定时重新注册与订阅。 
e) 提供者断开时,此时如果消费者只有这个提供者提供服务,则不可以进行服务的远程调用;注册中心断开时,消费者还可以通过本地缓存的提供者列表进行通信,但不可以进行新服务注册与订阅。 
f) 当提供者、消费者、注册中心之间无法检测中断开事件时(如:网络断线、网络抖动等),可以通过心跳机制探测连接是否可用,若不可用,提供者端执行断开连接操作、消费者端执行重连操作、注册中心也会进行服务的销毁操作。 
g) 数据库注册中心健壮性分析与设计(非集群): 
i. 每个消费者、提供者(对于注册中心来说它们都是消费者)在向注册中心注册、订阅服务之前,都在其JVM中缓存了一份列表,以备突发情况,当注册中心宕机或不可用时,所有提供者与消费者会重新尝试注册、订阅所有服务及无限次重连注册中心。 
ii. 注册中心接保存着提供者与消费者的注册与订阅服务信息,其中订阅覆盖服务,提供者订阅提供者、覆盖、路由服务信息,而注册中心是通过反向代理推送服务信息给他们。注册中心在其JVM中保存一份所有注册、订阅的服务信息,当数据库不可用或宕机时,注册中心正常对外提供服务,只是暂时无法将服务信息持久化到数据库,当数据库服务器恢复时,注册中心重试程序会自动将JVM中的所有服务信息写回到数据库保存。 
iii. 当注册中心宕机重启时,所有消费者提供者会重连此注册中心,这样对注册中心造成的冲击非常大,Dubbo建议的方案是重连时间设置成随机几分钟内。 
iv. 另外再说一点,其实在消费者也保存一份提供者服务信息在JVM与本地磁盘上,当注册中心宕机时,不会影响消费者正常使用,甚至可以重启消费者,只是消费者无法发现服务变更与新提供者

猜你喜欢

转载自blog.csdn.net/zhanjianshinian/article/details/72974498