protobuf在api接口定义中有很广泛的使用。我们设计一个api接口,往往关注一些常用的指标:压缩率(影响到传输带宽和传输时间)、压缩效率、易读性、可扩展性、支持的编码语言丰富程度。之所以protobuf得到广泛的使用,是由于在这些指标中,protobuf都得到不错的成绩或者平衡性。
总所周知:不同的进程通过接口传输结构体数据。结构体是以最原始的格式存储在内存中,传输这块数据还需要保持网络和主机的字节序,还要解决成员内的数据边界、结束符问题。
我们可以用用一行字符串格式,表示ipv4地址。
更复杂的结构体表示法,使用类似人脑易于理解的语言,XML语法。XML做到了人易读,但是机器执行效率低,内存空间冗余多。层级深,定位到深度很深的元素难找。
XML所见即是所得,proto则采用了更有效的二进制表达,proto的数据才用高度压缩的二进制,这些二进制通过特定的编码格式有空间效率地组织在一起。proto才用二进制编码传输和接口描述性文档 共同组成。比如说网络传输的只是一个二进制,但是根据文档对应出这个二进制是有特殊含义,比如一个结构体的key。按照双方约定的共同文档,双方达到一致的数据。
同时proto的工具包有自动生成接口代码,兼容旧代码,便于扩展新元素,读写结构体的接口
bool, int32, float, double, string,还有自定义类型
类型可以嵌套,但不像oop,没有类的继承关系
required:必须带的字段,它的反义是optional,字段是未初始化状态,在解析的时候不会进行检查。required对代码的变动兼容性要求高些,可能让消息重新定义使用不方便,不太建议
optional:默认是0,空字符串,bool是false
repeated:0或者多个元素
reserved:序列号或者名字
保留这些位置,留给以后升级用
message Foo { reserved 2, 15, 9 to 11; reserved "foo", "bar"; }
设置optional的默认值
optional int32 result_per_page = 3 [default = 10];
enum允许相同值,设置这个allow_alias=true
enum EnumAllowingAlias { option allow_alias = true; UNKNOWN = 0; STARTED = 1; RUNNING = 1; } enum EnumNotAllowingAlias { UNKNOWN = 0; STARTED = 1; // RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside. }
如果相同的成员被赋予了多次实例,比如多次赋值同一个key,对数字和字符串,会取最后一个,对于能merge的 message,会进行合并
得益于二进制压缩,比传统的xml和json。protobuf会有很明显的空间减少来存储传输数据。
Varint使用可变长度表示数字。varint的每一个字节的最高位有特殊意思。代表连续不连续。如果是1,表示后续的字节也是varint的一部分。如果是0,表示到此为止。每个字节的其他7位用来表示真实的数字。这样做的好处是,明显对于很小的数字来说,所需要表示一个int32的字节数变少了,从1个字节到5个字节。最差的情况就是5个字节表示一个int32大数。但是从概率来看,会大概率压缩数据。
负数的zig-zag编码
我们知道在计算机领域,负数是用一个补码表示,负数的最高位是1,所以即使是-1这个绝对值很小的数字,在编码来看也是需要占满四个字节。protobuf才用的zig-zag编码,认为以一种正负相插的编码,能大概率地减少压缩数据。
编码器 | 编码后 |
---|---|
0 | 0 |
-1 | 1 |
1 | 2 |
-2 | 3 |
2 | 4 |
key:数字+wire_type,共用一个byte。byte的前5位是这个成员的位置,也就是例子中的age=1的1.wire_type是后3位。wire_type的类型可以有以下几种
message {
int age = 1;
}
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 |
也就是说,第一个byte的构成方式是(field_number << 3) | wire_type。这种用fiel_number来组织结构体成员的好处是对于可选的 Field,如果消息中不存在该 field,那么在最终的 Message Buffer 中就没有该 field,这些特性都有助于节约消息本身的大小。
对比XML字符串的IDL, 需要做DOM解析树,需要解析字符串,需要完成词法文法分析等大量消耗 CPU 的复杂计算
而 Protobuf,将二进制序列,按照约定格式读取即可。从设置是通过几个位移操作,比xml的字符串解析,速度非常有优势。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。