为什么要序列化和反序列化
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
的各个子类并非实际的做序列化和反序列化动作,而是分别委托给了ObjectOutput
和ObjectInput
来完成。
下面看看ObjectOutput
和ObjectInput
的结构,结构不复杂。内部各自实现各自的序列化和反序列化动作。
总结下序列化模块,这个模块从结构上非常简单,基本没有对其他模块的依赖,因为它在最底层。其次,它对外只需要暴露 Serialization
接口就能对外发布服务。
编码和解码
先看下Codec2
的类图
从类图本身看,结构并不复杂,只是DubboCodec
的继承链路会比较长。那是因为它有自定义报文。所有的编码和解码都需要自己实现。内部实现的代码结构也比较复杂,说实话,那部分代码写的并不优雅。在优雅代码汇总篇中有详细分析。
DubboCodec
这个类的继承和实现关系,有些奇怪,都已经做了一些列的继承关系,其父类也实现了Codec2
接口。它自己还去实现了Codec2
。我也没有猜到作者的用意是啥,或者只是忘记删除了。
Thrift
,ThriftNative
都是自己实现编码和解码操作。那开发者也可以实现自己的编码和解码操作,并且配置上去。
看看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.
说明请求和返回报文都会把序列化方式来回传递。
总结
编码和序列化模块相对比较独立,主要是作为工具模块而存在。可扩展性也很强,开发者可以自由做横向扩展。不过有啥必要呢。