基于 Java 提供的对象输入/输出流ObjectInputStream和ObjectOutputStream,可以直接把java对象作为可存储的字节数组存入文件,也可以传输到网络上。对于程序员来说,基于JDK默认的 序列化 机制可以避免操作底层的字节数组,从而提升开发效率。
Java序列化的目的主要有两个:
-
网络传输
-
对象持久化
一、Java序列化的缺点
Java序列化从JDK 1.1版本就已经开始提供,他不需要添加额外的类库,只需实现java.io.Serializable并生成序列ID即可,因此,他从诞生之初就得到了广泛的应用。
但是在远程服务调用 RPC 时,很少直接使用Java序列化进行消息的编解码和传输,这又是为什么呢?
1.无法跨语言
无法跨语言,是Java序列化最致命的问题。对于跨进程的服务调用,服务提供者可能会使用C++或者其他语言开发,当我们需要和异构语言进程交互时,Java序列化就难以胜任。
由于Java序列化技术是Java语言内部的私有协议,其他语言不支持,对于用户来说它完全是黑盒。对于Java序列化后的字节数组,别的语言无法对其反序列化,这就严重阻碍了他的应用。
事实上,目前几乎所有流行的Java RPC通信框架,都没有使用Java序列化作为编解码框架,原因就在于它无法跨语言,而这些RPC框架往往需要支持跨语言使用。
2.序列化后的码流太大
我们评判一个编解码框架的优劣时,往往会考虑以下几个因素。
-
是否支持跨语言,支持的语言种类是否丰富
-
编码后的码流大小
-
编解码的性能
-
类库是否小巧,API使用是否方便
-
使用者需要手工开发的工作量和难度
在同等情况下,编码后的字节数组越大,存储的时候就越占空间,存储的硬件成本就越高,并且在网络传输时就更占带宽,导致系统的吞吐量降低。Java序列化后的码流偏大也一直被业界所诟病,导致他的应用范围受到了很大限制。
3.序列化性能太低
二、业界主流的编解码框架
1.Google的Protobuf介绍
Protobuf全称Google Protocal Buffers,它由 谷歌 开源而来,在谷歌内部久经考验。它将数据结构以.proto文件进行描述,通过代码生成工具可以生成对应数据结构的 POJO 对象和Protobuf相关的方法和属性。
他的特点如下:
-
结构化数据存储格式(XML、 JSON 等)
-
高效的编解码性能
-
语言无关、平台无关、扩展性好
-
官方支持Java、C++和Python三种语言
首先我们来看一下为什么不使用XML,尽管XML的可读性和扩展性非常好,也非常适合描述数据结构,但是XML解析的时间开销和XML为了可读性而牺牲的空间开销都非常大,因此不适合做高性能的通信协议。Protobuf使用二进制编码,在空间和性能上具有更大的优势。
Protobuf另一个比较吸引人的地方就是他的数据描述文件和代码生成机制,利用数据描述文件对数据结构进行说明的优点如下:
-
文本化的数据结构描述语言,可以实现语言和平台无关,特别适合异构系统间的集成
-
通过标识字段的顺序,可以实现协议的前向兼容
-
自动代码生成,不需要手工编写同样数据结构的C++和Java版本
-
方便后续的管理和维护。相比于代码,结构化的文档更容易管理和维护
2.Facebook的 Thrift 介绍
Thrift主要由5部分组成。
-
语言系统以及 IDL 编译器:负责由用户给定的IDL文件生成响应语言的接口代码
-
TProtocol:RPC协议层,可以选择多种不同的对象序列化方式,如JSON和Binary
-
TTransport:RPC 传输层 ,同样可以选择不同的传输层实现,如socket、NIO、MemoryBuffer等
-
TProcessor:作为协议层和用户提供的服务实现之间的纽带,负责调用服务实现的接口
-
TServer:聚合TProtocol、TTransport和TProcessor等对象
与Protobuf比较类似的是,Thrift通过IDL描述接口和数据结构定义,它支持八种Java基本类型、Map、Set和List,支持可选和必选定义,功能非常强大。因为他可以定义数据结构中字段的顺序,所以他也可以支持协议的前向兼容。
Thrift支持三种比较典型的编解码方式
-
通用的 二进制 编解码
-
压缩二进制编解码
-
优化的可选字段压缩编解码