聊聊Protocol Buffers
source link: http://vearne.cc/archives/39117
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
聊聊Protocol Buffers
版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | http://vearne.cc
Protocal Buffers
是google推出的一种序列化协议。由于它的编码和解码的速度,已经编码后的大小控制的较好,因此它常常被用在RPC调用中,传递参数和结果。比如gRPC
。
Protocal Buffers
的实现非常简单,本文将对比JSON协议,来聊聊Protocol Buffers的实现以及它高性能的秘密
2.1 减少传输量(字段名和定界符)
汽车类在Golang中的定义
type Car struct {
Age int32 `json:"age"`
Color string `json:"color"`
Price float32 `json:"price"`
}
JSON字符串表示
{
"age": 10,
"color": "red",
"price": 15.2568983
}
1)”{” 、”}”、”[“, “]”、 双引号、”,” 、”:” 是为了把字段与字段之间,以及字段的名称和值分隔开。它们不是必须的。
2)字段的名称”age”、”color”、”price”也不是必须的。
如果发送方和接收方都对对象的定义是明晰的,那么字段的名称也不要传递
Protocol Buffers
对象定义
message Car {
int32 age = 1;
string color = 2;
double price = 3;
}
每个字段都有一个编号,比如在例子中,age是1,color是2,price是3
接收方只要拿到编号,就可以知道需要解析的是哪个字段,它对应的名字甚至是字段值的长度
下图是对Protocol buffers
编码的说明 图1
Protocol buffers
有点TLV的意思(type-length-value)
FieldInfo
包含了存储field_number
(字段编号), data_type
表示字段类型
- 对于
64-bit
32-bit
得到类data_type
,也就得到了长度 - 对于
Varint
可以在解析的过程得到value - 对于 类似
Length-delimited
稍微有点特殊,有额外的字段length
表示value字节的长度
注 Varint
是对整型的变长表示,它与ES中使用的整型压缩算法是完全一致的。参见我的文章VINT–针对INT型的压缩格式
由于Protocol Buffers
有type和length信息的存在,因此无需字段名称和JSON中的”{“等定界符
2.2 减少传输量(整型和浮点数)
由于JSON属于文本型协议,因此它传输的数据都是字符
- 对于较大的整数,var int32 age = 123456789 传输时会变成”123456789″ 需要消耗9个字节
- 对于浮点数,如果出现小数部分 var float32 price = 15.2568983
传输时,会变成”15.2568983″
在Protocol Buffers
中,int32按Varint
存储,平均开销不到3个字节,而float32按照固定4字节存储,这样一来就比JSON少了不少
2.3字段可选
Protocol Buffers
中允许指定某个字段是optional
(可选的)。如果该字段没有值,则编码时,这个字段不会占用任何字节。
在一些语言的JSON库包中,如果解码时,该字段在JSON字符串中不存在,则会直接报错。
2.4 解码时的优势
2.4.1 跳过数据结构
JSON 是一个没有 header 的格式。因为没有 header,JSON 需要扫描每个字节才可以定位到所需的字段上。中间可能要扫过很多不需要处理的字段。
message PbTestWriteObject {
repeated string field1 = 1;
message Field2 {
repeated string field1 = 1;
repeated string field2 = 2;
repeated string field3 = 3;
}
Field2 field2 = 2;
string field3 = 3;
}
message PbTestReadObject {
string field3 = 3;
}
消息用 PbTestWriteObject 来编码,然后用 PbTestReadObject 来解码。field1 和 field2 的内容应该被跳过。
这是一个非常极端的例子,回顾图1中的示例,在Protocol Buffers
中除了Varint
类型,其余类型,都能直接得到长度信息,因此可以直接跳过不需要解析的字节,效率大大提高
2.4.2 字符串的处理
对于string类型的数据,JSON一般而言还需要支持unicode
和UTF8 2种编码
对于Golang,string本身就是UTF8编码的字节,因此在解码时,直接做memcopy
就行
在Protocol Buffers
在极端场景下对JSON的速度优势,可以达到5倍左右,但是它本身与Gzip
等比较,不算是一种压缩算法。它可以被表述为更为紧凑的序列化协议。对于针对它序列化的结果,再使用其它压缩算法进行一步压缩。
4. 代码参考
对于不同类型字段的序列化(编码)主要在
table_marshal.go 中的typeMarshaler
函数
针对 32-bit
的编码
func appendFixedS32Ptr(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
p := ptr.getInt32Ptr()
if p == nil {
return b, nil
}
b = appendVarint(b, wiretag)
b = appendFixed32(b, uint32(*p))
return b, nil
}
针对 string 的编码
func appendStringValue(b []byte, ptr pointer, wiretag uint64, _ bool) ([]byte, error) {
v := *ptr.toString()
b = appendVarint(b, wiretag) //
b = appendVarint(b, uint64(len(v)))
b = append(b, v...)
return b, nil
}
如果我的文章对你有帮助,你可以给我打赏以促使我拿出更多的时间和精力来分享我的经验和思考总结。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK