最近听到有朋友公司用这个Dubbo,正好想了解一下源码。经过一小段时间分析,发现知识点非常多,很有价值。包括:动态代理,spring整合,各种设计模式,线程池,锁,netty这样基于nio的tcp框架,协议的设计,当然最重要的是一种解决问题的思路,就吧这些体会记录下来:
许多资料书只是工具书,只有在源码的综合使用过程中才能有更深的体会,才能碰到问题时,有直觉上的方案!
也许已经写过很多很多代码,真正有价值的代码真的多吗?Dubbo源码确实有助于提高整体水平!
一、Dubbo要干什么?
远程跨应用调用。就是这边有接口,那边有实现。那么这边产生一个代理对象(即:Proxy.newProxyInstance)实现接口,对于任何请求,都把请求的东西(主要有方法名,参数类型,参数值)发出去。那边的应用会根据一些信息,找到一个exporter,而它持有一个invoker(即:new AbstractProxyInvoker,这个不是通常生成动态代理的标准invoker,而且它的方法invoke()也可以换成其它名字。),因为它持有真正的接口实现类,所以再根据方法名字反射得到值,再返回给请求方。
二、知识点
1.动态代理
比如jdk动态代理,这个资料很多。简单的使用,就是有一个接口,写一个invoker,持有实现类。再通过Proxy.newProxyInstance返回一个代理对象,它实现了所有的接口,而所有的调用都变成invoke,并传来方法与参数。这时再对持有的实现类反射调用就可以了。在反射调用前后可以做点什么,这点很重要(事务啊,计时啊,推动工作流啊,读缓存啊.....)!!
2.层层包装
这算设计模式了。主要用在Dubbo中的各个handler之间。举个现实栗子,公司门口有人送信来,首先门卫那到,它有一个处理方式,如果是垃圾信就丢掉。重要的交给收发人,同时把送信人告诉收发人。收发人看到是简单问候,直接就让送信人回复了,如果是重要的信,再交给领导,同时把送信人信息告诉领导。领导拿到后,进行处理,结果直接给送信人,这样就处理完了。如果有一天要与歪果人打交道,在门卫收信前设置一个翻译人,他收到,如果是中文就给门卫,如果是外文,先翻译再给门卫,当然翻译接触送信了,所以这时由他把送信人信息给门卫。每个人都有一个handle方法,每个人除了领导都引用下一个处理人,同时把送信人信息交给对方。 有句话说,人的本质是各种社会关系的总和,感觉在面向对象的编程中,对象的本质是各种引用关系的总和。
3.内部类与模板方式
一般抽象类中写公共方法,不同的了类中写不同的方法。如果一个方法中有一部分相同,一部分不同,那这个方法就拆成2个方法,不同的部分成为抽象方法在子类中实现。有时候这个不同,只有在使用时都知道有什么不同,那干脆就只在new这个抽象类时补充上不同的那个抽象方法,有点象匿名类(new AbstractProxyInvoker的时候就是这样。见JdkProxyFactory类。)。有时候这个不同的部分包装成实现通用接口的对象,那就是回调了,比如hibernateTemplate中经常用的。看情况选用吧。
4.从具体到抽象
通常一个大的应用中会用到已有的东东。为了灵活性,会使用不同的已发明的轮子,但轮子尺寸不一,咋办?包装啊。比如说:我要把消息给远方的朋友,我可以打电话,可以发email,可以QQ,可以写信。利用的都是已经有的通讯方式,不过感觉麻烦,我只想说一句话啊。那干脆雇佣几个人,一个会写信,一个会打电话,一个会email,那我只要选择其中一个人(好象是自动发现机制吧),对他们说句话,就OK了,其它事情他们搞定。Dubbo可以用netty,也可以用其它方式作为transport的工具,反正都是已有的轮子,包装起来用。Dubbo会有NettyClient,GrizzlyClient...反正他们都实现通用的方法。
5.netty通讯
netty是基于nio的通讯框架。基本原理不说了,重点体会NettyClient、NettyServer、NettyHandler、NettyChannel、以及和ChannelHandler的关系。NettyClient的两面性特别注意。NettyHandler构建的时候把NettyClient当成ChannelHandler来用,因为它持有ChannelHandler,而它本来是个client。
NettyHandler可以监控任何nio通讯事件,监控到了,它就让引用的ChannelHandler(NettyClient)来处理事情,并把【来信通道】告诉处理的人。而处理的人就象前面介绍的门卫、收发人,领导那样依次处理。如果是要回复的处理(twoway),就直接用【来信通道】把最后的处理结果返回就OK了。
从命名看功能:client一般有close(),connect(),reset()等方法,Channel一般有send()等功能,ChannelHandler一般是利用Channel来做些事情。
6. HeaderExchanger
当把客户端的调用信息,远程发送给服务器时,当然需要netty这样的通讯工具来帮忙。比如我们经常要让远方的朋友帮忙做事时,有时候让邮局送信,有时候让快递送信,有时候让镖局送东西。太麻烦了,我干脆在自己这边成立一个送信办事处,朋友那边成产一个办事处。办事处之间定时通讯一下(心跳),看着通讯有效不。服务点开张的时候,默认选择一个邮局(nettyclient)来用。办事处的通讯也有一些功能象client,也有一些功能象channel。所以就分成HeaderExchangeClient与HeaderExchangeChannel吧。办事处不象邮局那样处理各种各样的东东,我们自己的办事处只处理自己的request与response。主要的包括心跳请求,办事请求与相应的响应,还设计请求的同步与异步,同步时请求线程进入wait。这些格式就自己来设计了。
7.netty的encode/decode
前面讲netty后,缺少了一个重要步骤。netty传输时,读写都是对ChannelBuffer操作,就算是临时仓库吧。writeBytes、readBytes是ChannelBuffer的功能。得到的byte[]需要相互转换request与response。设计好的request转成byte[]是这样的,【头部】一共16位byte。01是叫magic码,2.3是请求标识或者twoway之类的,4-11是请求的ID,12到15是记录后面的数据的长度。response头部也是magic码,2是事件标识,3是响应码,其它不一一道来了。这样,来了请求或者响应,可以组成一堆byte[]放仓库,当仓库里来了一堆byte[],也可以分析出是啥东东。也有分析不出来的,返回DecodeResult.NEED_MORE_INPUT这个枚举对象。当你用netty实现httpserver时,你可以用现成的decode得到一个httprequest对象。
8 重要的对象关系
HeaderExchangeClient拥有一个NettyClient,还拥有一个HeaderExchangeChannel。但HeaderExchangeChannel做的事情还是找NettyClient来做,还记得前面说过NettyClient的两面性,即象client,又象channel。
NettyClient更厉害了,它除了是client外,因为持有chanel(最核心的netty里的chanel),可以直接用chanel发送东西出去。构造的时候因为持有外部传给它的ChannelHandler,本来已经包装过了(new DecodeHandler(new HeaderExchangeHandler(handler))),它还再包装了一下(wrapChannelHandler(url, handler)),所以可以处理nettyhandle中的通道事务。nettyhandle是NettyClient生成的用来守着通道的,告诉它,有情况来找我,因为我持有ChannelHandler,我也可以变身ChannelHandler。
9 重要的处理过程
层层包装的ChannelHandler处理channel事务,包装的同时还传递着channel供其使用。从这两句看的出来:new DecodeHandler(new HeaderExchangeHandler(handler) 与new MultiMessageHandler(new HeartbeatHandler(....)。最原始的Handler是DubboProtocol中的ExchangeHandlerAdapter的匿名子类,在它外面一直在包装。等到了nettyhandle用NettyClient来处理的时候,要一层一层的从外到里处理了。
比如:HeartbeatHandler处理时,发现是心跳请求,直接产生一个心跳回复就OK了,用channel发出去。HeartbeatHandler不会再传递给内部了。如果不是心跳请求,传给HeaderExchangeHandler处理,并把channel给它。如果HeaderExchangeHandler发现是响应,它要把响应结果放好,并唤醒请求时等待的线程,说:返回值来了,醒醒,该干活了。如果发现是真正的请求,HeaderExchangeHandler还要把channel和请求转给DubboProtocol中最原始的Handler来处理。原始的Handler就不传递了,调用reply()来找到exporter,再找到invoker,处理后返回值。
三、把知识点串起来。
下篇再用代码串起来分析...有遗漏的知识点也再补充(现在想到了线程池与lock)。