Dubbo协议解析



         Dubbo协议设计参考了TCP/IP协议,包括协议头和协议体两部分。16字节报文头主要携带了魔法数(0xdabb,用于分割两个不同请求),以及当前请求报文是否是Request、Response、心跳和事件的信息,请求时也会携带当前报文体内序列化协议编号,另外还有请求状态、请求唯一表示和报文体长度。

dubbo支持多大报文_继承关系

         0~7           魔数高位   存储0xda
         8~15         魔数低位   存储0xbb
         16             数据包类型   是否为双向的RPC调用(比如方法调用有返回值),0位Response,1位Request
         17             调用方式    16位为1的情况下有效,0为单向调用,1为双向调用
         18             事件标识     0为请求/响应包,1位心跳包
         19~23       序列化器编号
         24~31       状态
         32~95       请求编码     8个字节存储RPC请求的唯一id,用来将请求和响应做关联
         96~127     消息体长度     4个字节消息体,存有Dubbo版本号、服务接口名、服务接口版本、方法名、参数类型、方法参数值和请求额外参数




Netty编解码器扩展




         Netty提供了两个基类,一个是ByteToMessageDecoder解码基类,一个是MessageToByteEncode编码基类,用于自定义协议扩展。



ByteToMessageDecoder



         ByteToMessageDecoder是一个抽象类,解码方法名decode,需要子类去实现。继承关系如下图:

dubbo支持多大报文_代码实现_02

         ByteToMessageDecoder继承了ChannelInboundHandlerAdapter,是一个Inbound入站处理器,关于ChannelPipeline事件传播机制前面几篇文章已经做了详细介绍,此处不再赘述,所以重点关注一下其channelRead方法,即读事件的传播,代码如下:

dubbo支持多大报文_代码实现_03

         其实现比较简单,主要是对ByteBuf解码,然后继续向下传播ChannelRead事件,解码具体实现在callDecode方法中,代码实现如下:

dubbo支持多大报文_java_04

         callDecode中会读取数据,然后会调用decodeRemovalReentryProtection继续解码,在decodeRemovalReentryProtection中我们看到了所期待的的调用子类的decode方法解码。



MessageToByteEncoder



         与解码器相对的,MessageToByteEncoder编码器,也是个抽象基类,提供了抽象方法encode需要子类去实现,继承关系如下图:

dubbo支持多大报文_继承关系_05

         由继承关系图可以看出,MessageToByteEncoder继承了ChannelOutboundHandlerAdapter,是一个outbound出站处理器,重点关注其write方法,代码实现如下:

dubbo支持多大报文_代码实现_06

         代码逻辑还是比较清晰,就是调用encode编码,然后向下传播write事件。

         到这里就看到了Netty对自定义编解码的扩展,下面介绍Dubbo对扩展的自定义实现。




Dubbo协议实现源码分析




         编解码器本质上都是handler,所以先从Netty启动是往ChannelPipeline中添加handler开始看,以服务端为例,NettyServer#doOpen方法代码如下:

dubbo支持多大报文_代码实现_07

         标准的一个Netty服务器写法,里边可以看到编解码都由NettyCodecAdapter维护,NettyCodecAdapter代码如下:

dubbo支持多大报文_java_08

         实现较为简单,维护了两个内部类,一个是InternalEncoder继承自MessageToByteEncoder,一个是InternalDecoder继承自ByteToMessageDecoder,实现了对应的编解码接口。以及维护了一个Code2类型的对象,Codec2也是一个扩展点,对应不同协议的实现,因为介绍Dubbo协议,所以此处是DubboCountCodec,DubboCountCodec代码如下:

dubbo支持多大报文_代码实现_09

         DubboCountCodec内部维护了一个DubboCodec,DubboCodec才是编解码具体实现,DubboCountCodec做的就是一次性读取更多完整报文编解码生成对象。DubboCodec继承关系如下图:

dubbo支持多大报文_java_10




encode源码分析



         先介绍encode,DubboCountCodec调用encode之后,其具体实现逻辑位于ExchangeCodec#encode方法,实现代码如下:

dubbo支持多大报文_继承关系_11


         首先对需要编码的消息类型进行判断,如果是Request则调用encodeRequest,如果是Response则调用encodeResponse,如果都不是,则调用父类TelnetCodec的encode方法。先展开分析下encodeRequest方法,代码如下:

dubbo支持多大报文_编解码_12


         实际上就是对开头所描述的Dubbo协议的构建,其中会调用encodeRequestData对RpcInvocation调用进行编码,代码如下:

dubbo支持多大报文_继承关系_13


         主要是将方法调用参数和值编码成字节流。包括框架版本、调用接口、方法名称、参数类型、参数值等信息。

         encodeResponse方法实现与encodeRequest方法实现十分相似,同样是对Dubbo协议字节流的构建,不再展开描述,主要介绍下其encodeResponseData方法,其data一半是Result类型,实现代码如下:

dubbo支持多大报文_dubbo支持多大报文_14


         主要完成对调用结果的序列化。



decode源码分析



         解码过程要比编码复杂一点,主要是需要解决粘包和半包问题,其实现位于ExchangeCodec#decode方法,实现代码如下:

dubbo支持多大报文_编解码_15

         解码主要分为解码16字节报文头和解码报文体,具体的消息体解码位于DubboCodec#decodeBody方法中,具体代码如下:

dubbo支持多大报文_代码实现_16

         解码分为对request的解码和response的解码,虽然看上去很长,其实只是请求类型的分类,以及根据decode.in.io的配置值判断是在I/O线程中直接解码,还是交由业务线程池去解码。

         请求消息解码具体实现位于DecodeableRpcInvocation#decode方法,代码实现如下:

dubbo支持多大报文_继承关系_17

         主要逻辑是获取各项参数封装到当前的RpcInvocation对象中。

         响应消息解码的具体实现位于DecodeableRpcResult#decode中,具体代码实现如下:

dubbo支持多大报文_代码实现_18

         根据响应标记位,对各种情况下的响应做处理,正常情况下将返回数据反序列化后存到result字段中。