序列化和反序列化

互联网的产生带来了机器间通讯的需求,而互联通讯的双方需要采用约定的协议,序列化和反序列化属于通讯协议的一部分。

  • 序列化: 将数据结构或对象转换成二进制串的过程
  • 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程

数据结构和对象:对于面向对象的语言,工程师所操作的一切都是对象(Object),来自于类的实例化。

二进制串:序列化所生成的二进制串指的是存储在内存中的一块数据。一般指byte[],而C++中的byte不是内置类型,使用unsigned char来代替。

序列化和反序列化的协议需要考虑的特性:

  • 通用性
    • 跨平台、跨语言
    • 流行程度
  • 强健性/鲁棒性
    • 成熟度
  • 可调试性/可读性
    • 序列化后的数据是否可读
  • 性能
  • 扩展性/兼容性
  • 安全性

JSON

JSON起源于弱类型语言Javascript, 它的产生来自于一种称之为”Associative array”的概念,其本质是就是采用“Attribute-value”的方式来描述对象。实际上在Javascript和PHP等弱类型语言中,类的描述方式就是Associative array。JSON的如下优点,使得它快速成为最广泛使用的序列化协议之一:

  1. 这种Associative array格式非常符合工程师对对象的理解。

  2. 它保持了XML的人眼可读(Human-readable)的优点。

  3. 相对于XML而言,序列化后的数据更加简洁。 来自于的以下链接的研究表明:XML所产生序列化之后文件的大小接近JSON的两倍。http://www.codeproject.com/Articles/604720/JSON-vs-XML-Some-hard-numbers-about-verbosity

  4. 它具备Javascript的先天性支持,所以被广泛应用于Web browser的应用常景中,是Ajax的事实标准协议。

  5. 与XML相比,其协议比较简单,解析速度比较快。

  6. 松散的Associative array使得其具有良好的可扩展性和兼容性。

典型应用场景和非应用场景

JSON在很多应用场景中可以替代XML,更简洁并且解析速度更快。典型应用场景包括:

1、公司之间传输数据量相对小,实时性要求相对低(例如秒级别)的服务。

2、基于Web browser的Ajax请求。

3、由于JSON具有非常强的前后兼容性,对于接口经常发生变化,并对可调式性要求高的场景,例如Mobile app与服务端的通讯。

4、由于JSON的典型应用场景是JSON+HTTP,适合跨防火墙访问。

总的来说,采用JSON进行序列化的额外空间开销比较大,对于大数据量服务或持久化,这意味着巨大的内存和磁盘开销,这种场景不适合。没有统一可用的IDL降低了对参与方的约束,实际操作中往往只能采用文档方式来进行约定,这可能会给调试带来一些不便,延长开发周期。 由于JSON在一些语言中的序列化和反序列化需要采用反射机制,所以在性能要求为ms级别,不建议使用。

Protobuf

Protobuf具备了优秀的序列化协议的所需的众多典型特征:

1、标准的IDL和IDL编译器,这使得其对工程师非常友好。

2、序列化数据非常简洁,紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。

3、解析速度非常快,比对应的XML快约20-100倍。

4、提供了非常友好的动态库,使用非常简介,反序列化只需要一行代码。

Protobuf是一个纯粹的展示层协议,可以和各种传输层协议一起使用;Protobuf的文档也非常完善。 但是由于Protobuf产生于Google,所以目前其仅仅支持Java、C++、Python三种语言。另外Protobuf支持的数据类型相对较少,不支持常量类型。

典型应用场景和非应用场景

Protobuf具有广泛的用户基础,空间开销小以及高解析性能是其亮点,非常适合于公司内部的对性能要求高的RPC调用。由于Protobuf提供了标准的IDL以及对应的编译器,其IDL文件是参与各方的非常强的业务约束,另外,Protobuf与传输层无关,采用HTTP具有良好的跨防火墙的访问属性,所以Protobuf也适用于公司间对性能要求比较高的场景。由于其解析性能高,序列化后数据量相对少,非常适合应用层对象的持久化场景。

它的主要问题在于其所支持的语言相对较少,另外由于没有绑定的标准底层传输层协议,在公司间进行传输层协议的调试工作相对麻烦。

对比

Protobuf 和json对比:

https://zhuanlan.zhihu.com/p/53339153

在JavaScript环境下,Protobuf 只比 JSON 快一点,尤其是在 JSON 开启压缩的情况下,而且 JSON 具有可读性,也更容易调试,所以一般都会选择 JSON。

但如果我们使用的是其他平台,在非压缩环境中使用 Protobuf 时,请求所花费的时间比 JSON 请求少78%。 这表明二进制格式的执行速度几乎是文本格式的5倍。

当 JSON 不是native环境(如NodeJS,浏览器)时,性能提升的非常大。 因此,当您遇到 JSON 的延迟问题时,请考虑迁移到 Protobuf。

当然Protobuf也有一些缺点,比如开发者远远少于Json。官方支持的语言较少,比如不支持Lua,只能使用第三方库。

Protobuf的反序列化

Protobuf 反序列化

Protobuf 的二进制流格式符合 TLV 标准,(tag length value),key 就是 tag,包含了字段序号和类型,计算方式如下:

1
key =  (field_number << 3) | wire_type

每种数据类型都有对应的 Type:

Type Meaning Used For
0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
1 64-bit fixed64, sfixed64, double
2 Length-delimited string, bytes, embedded messages, packed repeated fields
3 Start group groups (deprecated)
4 End group groups (deprecated)
5 32-bit fixed32, sfixed32, float

所以 tag 的低3位就是 wire_type, tag 右移3位就是 field_number。

二进制数据中 tag 后面跟的是 length 和 value,下面看一个简单的例子。

1
2
3
message Test4 {
repeated int32 d = 4 [packed=true];
}

比如有一个 Test4 结构,其中字段 d 中有三个元素,分别是3,270和86942。那么序列化的数据如下:

1
2
3
4
5
22        // key (field number 4, wire type 2)
06 // payload size (6 bytes)
03 // first element (varint 3)
8E 02 // second element (varint 270)
9E A7 05 // third element (varint 86942)

此外,Google提供了官方的直接解析工具,可以在没有 proto 的情况下解析

1
protoc --decode_raw

更详细的内容可见:https://developers.google.com/protocol-buffers/docs/encoding

参考文章:https://tech.meituan.com/2015/02/26/serialization-vs-deserialization.html