dubbo的编码和序列化

1,438 阅读5分钟

为什么要序列化和反序列化

RPC过程必然要序列化和反序列化。

RPC是远程过程调用,A调用B,要经过网络,就会有数据传输,那就得在A端把请求参数序列化之后通过网络送到B端,B端进行反序列化。然后指向B服务,返回结果首先要在B端做序列化,之后通过网络送到A端,A端进行反序列化。这是一个完整的请求过程。分别产生2次序列化和反序列化动作。

为什么要编码和反编码

A调用B时,会传递信息,其中有一些公共的信息(包括序列化方式,序列化id,报文内容长度,这是请求报文还是返回报文等等)每次都要传递,非常规范,并且与业务无关。那我们就希望把它封装起来,在框架层面统一约定和处理。

这个过程类似于ISO7层协议模型在网络层的处理方式,分层处理。关于Dubbo报文的详细解释在Dubbo官方文档-实现细节中有详细说明,请查阅。

我觉得光看Dubbo官方文档的说明,还没法有非常强烈的感觉。这边贴了张它的图,并且拦截了一个实际的案例报文案例来分析一下。

上面这张图就是Request请求报文的截图,不太好看。仔细分析,会发现与Dubbo官方文档的说明是能对上的。

Magic High:11011010(0xda)

Magic Low:10111011(0xbb)

后面8位分别代表4个信息,1.请求报文,2.需要返回,3.非事件类型,4.用fastjson做序列化

64位Request ID都是0

报文长度:10111110 = 190

后面就是版本号,服务名字等等,每个信息之间都是以换行符来分隔0x0a。下面再把这个二进制对应的16进制图也贴一下。

序列化的结构设计

序列化模块的类结构比较简单,Serialization是接口,其他各个都是序列化的具体实现类。

@SPI("hessian2")// hessian2是默认实现
public interface Serialization {

  	// 获取序列化写入器
    @Adaptive
    ObjectOutput serialize(URL url, OutputStream output) throws IOException;
		
  	// 获取反序列化读取器
    @Adaptive
    ObjectInput deserialize(URL url, InputStream input) throws IOException;
}

发现Serialization的各个子类并非实际的做序列化和反序列化动作,而是分别委托给了ObjectOutputObjectInput来完成。

下面看看ObjectOutputObjectInput的结构,结构不复杂。内部各自实现各自的序列化和反序列化动作。

总结下序列化模块,这个模块从结构上非常简单,基本没有对其他模块的依赖,因为它在最底层。其次,它对外只需要暴露 Serialization接口就能对外发布服务。

编码和解码

先看下Codec2的类图

从类图本身看,结构并不复杂,只是DubboCodec的继承链路会比较长。那是因为它有自定义报文。所有的编码和解码都需要自己实现。内部实现的代码结构也比较复杂,说实话,那部分代码写的并不优雅。在优雅代码汇总篇中有详细分析。

DubboCodec这个类的继承和实现关系,有些奇怪,都已经做了一些列的继承关系,其父类也实现了Codec2接口。它自己还去实现了Codec2。我也没有猜到作者的用意是啥,或者只是忘记删除了。

ThriftThriftNative都是自己实现编码和解码操作。那开发者也可以实现自己的编码和解码操作,并且配置上去。

看看Codec2的接口

public interface Codec2 {
		// 编码行为
    @Adaptive({Constants.CODEC_KEY})
    void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException;
		// 解码行为
    @Adaptive({Constants.CODEC_KEY})
    Object decode(Channel channel, ChannelBuffer buffer) throws IOException;
}

Codec2接口就2个行为,分别是编码和解码,很容易理解。但子类对这两个行为的实现却是很复杂,这边不展开讲解了,主要怕我自己写吐了。有兴趣的朋友可以自己追踪下代码,也没必要逐行折腾,那部分代码写的比较乱,会看的比较头疼。

看到Codec2的一个子类CodecAdapter,从字面意思可以理解它是适配器模式。追踪下源码会发现它是用来适配Codec(上一代)的编码和解码的。这边只是引导下,不展开说了。

编码和解码其中一个步骤就是序列化和反序列化,编码模块依赖序列化模块,看下依赖的衔接点。下面贴了CodecSupport的代码,因为这个类的代码比较长,所以省去了大堆的代码,只是示意下。有兴趣的朋友自己追踪下源码。

public class CodecSupport {
		// ----------------------省略一堆代码-------------------------
    public static Serialization getSerializationById(Byte id) {
        // ----------------------省略一堆代码-------------------------
    }

    public static Serialization getSerialization(URL url) {
				// ----------------------省略一堆代码-------------------------
    }

    public static Serialization getSerialization(URL url, Byte id) throws IOException {
        // ----------------------省略一堆代码-------------------------
    }
   // ----------------------省略一堆代码-------------------------
}

通过CodecSupport的三个静态方法可以方便获取到Serialization的具体实例对象。然后做序列化和反序列化操作。

看到这边的参数(Byte id),可能会有些疑惑。这个时候要去看下Dubbo官方文档-实现细节。会看到

Serialization ID (5 bit)

Identifies serialization type: the value for fastjson is 6.

说明请求和返回报文都会把序列化方式来回传递。

总结

编码和序列化模块相对比较独立,主要是作为工具模块而存在。可扩展性也很强,开发者可以自由做横向扩展。不过有啥必要呢。