谈谈数据交换格式

这周末我们来看看数据交换格式:JSON(BSON)、Protocol Buffers和MessagePack。每当客户端和服务器、后端服务与服务之间通信时候,都会交换数据,如何将本机程序内存中的数据交给远端程序使用,同时不受语言、架构等限制就成为了一个问题。我们将对象转化为可以存储或传输的形式这个过程称为序列化,而其逆向过程称为反序列化

JSON

这或许是大家最熟悉的一种格式了,JSON格式广泛用于C/S架构中通信,其具有良好的可读性,一段样例JSON如下:

1
2
3
4
5
6
7
8
{
    "a": "a string",
    "b": 2,
    "c": {
        "d": [1, 2, 3]
    },
    "e": true
}

JSON可以传递数字、布尔、数组、字符串、对象、空类型的数据,但是对于二进制数据不太方便,除非你进行编码(如Base64)或者转换成数组。由于是字符串格式,JSON可读性较好,但是处理速度并不理想。

顺带,JSON发展于2000年初。

BSON

BSON目前主要运用于MongoDB中,BSON的含义即“二进制JSON”,相较于JSON,BSON使用二进制格式来进行编码,参考自JSON and BSON,一个BSON和JSON的样例如下:

1
2
3
4
5
6
{"hello": "world"} 
\x16\x00\x00\x00           // total document size
\x02                       // 0x02 = type String
hello\x00                  // field name
\x06\x00\x00\x00world\x00  // field value
\x00                       // 0x00 = type EOO ('end of object')

相较于JSON,BSON通过二进制表示了数据的类型和长度,这加快了遍历速度。同时BSON支持了JSON不支持的日期和二进制数据。

Protocol Buffers

Protocol Buffer是2008年由Google开发的序列化协议,其设计目标就是简单和性能。Protocol Buffer需要双方约定好消息的定义(.proto文件),一个示例如下:

1
2
3
message Test1 {
  optional int32 a = 1;
}

假如a的值为150,你会得到如下的序列化结果:

1
2
08 96 01
00001000 10010110 00000001

我们怎么来看呢?对于第一个字节代表Tag和类型,前5位代表Tag即上面的1,之后000三个bit代表其类型是VARINT,作为整数、无符号整数、布尔、枚举类型的统称。

1
2
3
4
5
6
10010110 00000001
拆分为
1 0010110 0 0000001
其中第一位表示接下来的字节是否继续表示该数据
除去MSB后,我们将数据变为小端序
10010110 = 150

对于类型和编码更多详细内容可以阅读官方文档:Encoding

我们可以发现ProtoBuf是用tag来表示数据的Key的,和其真正名称无关。ProtoBuf是二进制格式不具有可读性,但是传输开销和(反)序列化开销较小。

MessagePack

MessagePack目前最新的版本是2009年发布的0.3.3版本,MessagePack专注于紧凑和简单。一个JSON到MessagePack的样例如下:

1
2
3
4
5
6
7
8
9
{"compact":true,"schema":0}

82 a7 63 6f 6d 70 61 63 74 c3 a6 73 63 68 65 6d 61 00

82: 1000XXXX 表示2*N个元素的map,这里的N是0010,表示4个元素的Map。
a7:101XXXXX 表示长度为XXXXX的固定字符串,这里长度为7,即后续的63 6f 6d 70 61 63 74代表compact
c3: 固定表示true
a6: 类似于a7,对应后续的73 63 68 65 6d 61
00: 0xxxxxxx 表示xxxxxxx正整数

对于更加详细的编码规则可以参考spec。MessagePack对应于JSON,但是支持二进制数组等JSON本不支持的格式。由于是二进制格式,其仍然可读性不佳,但是性能较JSON好。