protocol buffers序列化的原理

3,512 阅读3分钟

介绍

protobuf 是一种google发明的数据序列化机制。官网的解释是:
protocol buffers(简称protobuf)是google的语言中立、平台中立、可扩展的机制,用来对结构化数据进行序列化,类似于xml,但是更小、更快、更简单。只要定义好如何结构化你的数据,就可以使用生成好的代码取写和读取各种数据流的数据,支持各种语言。

Varint

protobuf定义的格式形如:

message Test1 {
  optional int32 a = 1;
}

Varint的序列化和反序列化过程如下。

ink-image.png

varint存在的意义:

当我们日常使用的数字都是很小的数字,并没有用完32bit位时,如果全都用32bit填0去存,会造成大量的存储浪费,varint变长整数的思想对于1、2、3这样的小数字能够缩小空间,但是如果经常使用的数字都是很大的整数,则返回会占用更多的空间。

protobuf - 键值对

protobuf的消息结构本质上一串KV键值对,序列化成二进制的时候key只存了字段的序号,至于字段的名字和类型都是解码端通过序号查proto文件获取到的。

因为protobuf的本质是一系列键值对,所以它的二进制结构形如下表,key是字段的序号+解析方式,value是一堆字符串。

keyvalue
field_number + Varintac 02
field_number + 64-bit12 34 56 78 22 34 56 78
field_number + Length-delimited07 [ 74 65 73 74 69 6e 67 ]
field_number + Start group(deprecated)
field_number + End group(deprecated)
field_number + 32-bit12 34 56 78

protobuf的key,实际上的格式如下。

(field_number << 3) | wire_type

key - wire type

wire type,protobuf解析二进制信息时根据这个类型决定后面读多少字节,字段类型与wire type的映射关系如下表。

TypeMeaningUsed For
0Varintint32, int64, uint32, uint64, sint32, sint64, bool, enum
164-bitfixed64, sfixed64, double
2Length-delimitedstring, bytes, embedded messages, packed repeated fields
3Start groupgroups (deprecated)
4End groupgroups (deprecated)
532-bitfixed32, sfixed32, float

Length-delimited

ink-image.png

Signed Integers(有符号的整数)

protobuf对于有符号整数的解析也是通过varint,通过varint确定解析多少个字节,但是如果将某个负数(如-1)直接当做32位无符号整数去序列化,那么对于-1这种负数来说,由于最高位符号位的存在,导致varint结果会很大,远超-1包含的信息量。因此protobuf采用了ZigZag编码方式对varint的结果进行编码,进一步压缩数据,过程如下。

ink-image.png

Optional And Repeated Elements

message Test4 {
  repeated int32 d = 4
}

ink-image.png

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

标识packed=true时,和上面不同的是,这种时候是把d用Length-delimited的方法序列化的,且只支持原始数字类型(varint、32-bit、64-bit)

其它

protobuf序列化后的字段顺序是不固定的,官方文档中列出了各种可能性,总之字段顺序是不固定的,注意两次序列化的结果,它们hash、CRC、FingerPrint可能完全不同。

references

Encoding  Base128 Varints, Explained | Hacker Noon

Encoding  |  Protocol Buffers  |  Google Developers