Protocol-Buffer
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Protocol Buffers
简介
PB全称Protocol Buffers(也简称 Protobuf) 是 Google 公司内部的混合语言数据标准,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。
它很适合做数据存储或 RPC 数据交换格式。
可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
目前提供了 C++、Java、Python 三种语言的 API。
目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。
他们用于 RPC 系统和持续数据存储系统。
一个简单的例子
这里有一个非常简单的.proto文件,定义的信息和类型如下:
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
使用PB的编译器编译生成应用程序对应的语言的数据访问的类,于是我们可能写出如下的C++代码:
Person person;
person.set_name("John Doe");
person.set_id(1234);
person.set_email("jdoe@");
fstream output("myfile", ios::out | ios::binary);
person.SerializeToOstream(&output);
然后读取数据的时候可以这样写:
fstream input("myfile", ios::in | ios::binary);
Person person;
person.ParseFromIstream(&input);
cout << "Name: " << () << endl;
cout << "E-mail: " << person.email() << endl;
语言相关规则
a)定义消息结构,例如上面的例子
message Person {
required string name = 1;
required int32 id = 2; // Which page number do we want?
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
b)每个变量必须制定如下的规则(rule):
required:必须只能有1个.
optional:可以有0个或者1个
repeated:可以有任意个
由于历史原因,repeated修饰的基本的数字的类型编码压缩的效率不
够好,但是我们可以加选项[packed=true]来提高效率,如下:
repeated int32 samples = 4 [packed=true];
下面是摘自文档的一段话:
Required Is Forever You should be very careful about marking fields as required. If at some point you wish to stop writing or sending
a required field, it will be problematic to change the field to
an optional field – old readers will consider messages without this field to be incomplete and may reject or drop them
unintentionally. You should consider writing
application-specific custom validation routines for your buffers instead. Some engineers at Google have come to the conclusion that
using required does more harm than good; they prefer to use only optional and repeated. However, this view is not universal.
c)每个变量必须一个数字标签(TAG),如上述例子,tag是用来在压缩后
的二进制数据格式中识别该变量的,一旦定义好消息结构并且已经在程序中进行使用的时候,不应该再更改变量的tag。
1-15占用一个字节,16-2047占用两个字节
Tag的最小值是1,最大值是229 - 1, 或者536,870,911.
(19000-19999也不可以使用,是预留给PB编译器使用的
d)多个消息类型可以定义在一个.proto文件中,如下:
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;
}
message SearchResponse {
...
}
e)optional规则的变量可以设置默认值
optional int32 result_per_page = 3 [default = 10];
f)枚举变量
g)使用其他类型的消息
message SearchResponse {
repeated Result result = 1;
}
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
h)导入其他定义
import "myproject/other_protos.proto";
i)嵌套类型
j)添加注释
k)Scalar type
PB扩展性
a)extensions
message Foo {
// ...
extensions 100 to 199;
}
其他用户可以导入该定义,并进行扩展
extend Foo {
optional int32 bar = 126;
}
使用时略有不同
Foo foo;
foo.SetExtension(bar, 15);
b)更新消息结构定义
•不修改已经存在的变量的tag编号
•任何新增加的变量都是用optional和repeated定义
•非required的变量可以移除,只要不在使用它的tag编号
•非required变量可以变为extension,只要确保它的tag编号和数据类型不变
•int32, uint32, int64, uint64, 和 bool 都相互兼容
•sint32 和 sint64 相互兼容
•string 和 bytes 相互兼容,只要bytes类型是有效的UTF-8编码
•如果bytes类型包含的是编码后的消息,那么嵌套的消息与bytes 类型兼容
•fixed32与sfixed32兼容, fixed64 与 sfixed64兼容.
•optional与repeated兼容
•修改默认值也是可以的
PB 编码讨论
ProtoBuf编码基础——Varints, varints是一种将一个整数序列化为一个或者多个Bytes的方法,越小的整数,使用的Bytes越少。
Varints的基本规则是:
a)每个Byte的最高位(msb)是标志位,如果该位为1,表示该Byte后面还
有其它Byte,如果该位为0,表示该Byte是最后一个Byte。
b)每个Byte的低7位是用来存数值的位
c)Varints方法用Litte-Endian(小端)字节序
举个例子:300用Varints序列化的结果是1010 1100 0000 0010,运算过程如下所示:
1010 1100 0000 0010->010 1100 000 0010(去标志位)->
000 0010 010 1100(调整字节序)-> 1 0010 1100 ->256+32+8+4=300(计算值)ProtoBuf中消息的编码规则:
a)每条消息(message)都是有一系列的key-value对组成的, key和value
分别采用不同的编码方式。
b)对某一条件消息(message)进行编码的时候,是把该消息中所有的
key-value对序列化成二进制字节流;而解码的时候,解码程序读入二
进制的字节流,解析出每一个key-value对,如果解码过程中遇到识别
不出来的类型,直接跳过。
这样的机制,保证了即使该消息添加了新的
字段,也不会影响旧的编/解码程序正常工作。
c)key由两部分组成,一部分是在定义消息时对字段的编号(field_num),
d)key的编码方式:field_num << 3 | wire_type
e)varint类型(wire_type=0)的编码,与第(1)部分中介绍的方法基本一致,
但是int32, int64和sint32,sint64有些特别之处:int32和int64就
是简单的按varints方法来编码,所以像-1、-2这样负数也会占比较多
的Bytes。
于是sint32和sint64采用了一种改进的方法:先采用Zigzag
方法将所有的整数(正数、0和负数)一一映射到所有的无符号数上,
然后再采用varints编码方法进行编码。
Zigzag映射函数为:
Zigzag(n) = (n << 1) ^ (n >> 31), n为sint32时
Zigzag(n) = (n << 1) ^ (n >> 63), n为sint64时
这样映射后再进行编码的好处就是绝对值比较小的负数序列化后的结果
占的Bytes数也会比较少。
f)64-bit(wire_type=1)和32-bit(wire_type=5)的编码方式就比较简单
了,直接在key后面跟上64bits或32bits,采用Little-Endian(小端)
字节序。
g)length-delimited(wire_type=2)的编码方式:key+length+content,
key的编码方式是统一的,length采用varints编码方式,content就
是由length指定的长度的Bytes。
h)wire_type=3和4的现在已经不推荐使用了,因此这里也不再做介绍。
基于PB的流的讨论
PB序列化之后的二进制数据只是简单的压缩之后的数据内容,没有对消息的头和尾做任何标记,这样我们在网络传输PB数据的时候,如果传输多个消息的话,如何解析就是程序员自己需要解决的事情了,最简单的方法是在往流写入消息之前,先写入该消息的大小,然后再写入消息本身,这样读数据的一段可以根据消息的大小读到一个完整的消息。
进一步的方案思考:如何做一个通用的基于PB的网络传输框架?
/Solstice/archive/2011/04/03/2004458.html
基于PB的强大的反射功能。
PB无法传输过大数据
官方文档介绍,PB不是为传输大数据而设计的,如果传输的消息大小大于1M的话,是该考虑其他方案的时候了。
PB提供了一个类,CodedInputStream(CodedOutputStream),用来方便向流写入或读取PB数据,但是这类的内部有限制,读取的总数据不能超过64M,如果超过的话,会拒绝接受进一步的数据。
PB的性能讨论
Protobuf 的优势
官方文档说protocol buffer有很多XML不具备的优点:
•简单;
•小巧:3-10倍
•效率高:20-100倍
•无二义性
有自动工具生成访问类
你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。
你甚至可以在无需重新部署程序的情况下更新数据结构。
只需使用Protobuf 对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。
它有一个非常棒的特性,即“向后”兼容性好,人们不必破坏已部署的、依靠“老”数据格式的程序就可以对数据结构进行升级。
这样您的程序就可以不必担心因为消息结构的改变而造成的大规模的代码重构或者迁移的问题。
因为添加新的消息中的 field 并不会引起已经发布的程序的任何改变。
Protobuf 语义更清晰,无需类似 XML 解析器的东西(因为 Protobuf 编译器会将 .proto 文件编译生成对应的数据访问类以对 Protobuf 数据进行序列化、反序列化操作)。
使用 Protobuf 无需学习复杂的文档对象模型,Protobuf 的编程模式比较友好,简单易学,同时它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf 比其他的技术更加有吸引力。
Protobuf 的不足
Protbuf 与 XML 相比也有不足之处。
它功能简单,无法用来表示复杂的概念。
XML 已经成为多种行业标准的编写工具,Protobuf 只是 Google 公司内部使用的工具,在通用性上还差很多。
由于文本并不适合用来描述数据结构,所以Protobuf 也不适合用来对基于文本的标记文档(如 HTML)建模。
另外,由于 XML 具有某种程度上的自解释性,它可以被人直接读取编辑,在这一点上 Protobuf 不行,它以二进制的方式存储,除非你有 .proto 定义,否则你没法直接读出Protobuf 的任何内容
(注:可编辑下载,若有不当之处,请指正,谢谢!)。