Protocol Buffer 简介
-
Google Protocol Buffer(简称Protobuf)是Google公司内部的混合语言数据标准,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。是一种可用于通讯协议、数据存储等领域的无关语言、无关平台、可扩展的序列化结构数据格式。
-
通过将结构化数据串行化(序列化),从而实现数据存储 / RPC数据交换的功能。
- 序列化:将数据结构或对象转化为二进制串的过程;
- 反序列化:将在序列化过程中生成的二进制串转化成数据结构或对象的过程。
序列化协议需要考虑些什么?
- 序列化之后的流大小(占用网络宽带)
- 序列化和反序列化的性能(CPU+内存等资源占用)
- 是否支持跨语言
protobuf特点
-
性能方面
- 体积小。序列化后,数据大小均缩小约3倍;
- 序列化速度快,比XML和Json快2~50倍;
- 传输速度快,因为体积小,传输起来带宽和速度会有优化;
-
使用方面
- 使用简单,protobuf编译器自动进行序列化和反序列化
- 维护成本低,多平台仅需要维护一份对象协议文件。
- 向后兼容好,即可扩展性好,不必破坏就数据格式就可以直接对数据格式进行更新;
- 加密性好,HTTP传输抓包只能抓到序列后的的二进制串。
-
使用范围
- 跨平台
- 跨语言
- 高可扩展性
protobuf的缺点
- 不适用于对基于文本的标记文档(如HTML)建模,因为文本不适合描述数据结构;
- 通用性差,protobuf只是Google公司内部使用的工具,没有被大众化;
- 以二进制数据流方式进行存储(不可读),需要通过proto文件才能了解数据结构。
指定字段规则
-
require: 格式良好的message必须包含该字段一次;
-
optional: 格式良好的message必须包含该字段零次或者一次;
-
repeated: 该字段可在格式良好的消息中重复任意多次(包括零次),其中重复的顺序会被保留。(由于一些历史原因,标量数字类型的 repeated 字段不能尽可能高效地编码。新代码应使用特殊选项 [packed = true] 来获得更高效的编码。)
repeated int32 samples = 4 [packed=true]; //注意:对 required 的使用永远都应该非常小心。如果你希望在某个时刻停止写入或发送 required 字段, 则将字段更改为可选字段将会有问题 - 旧读者会认为没有此字段的邮件不完整,可能会无意中拒 绝或删除它们。你应该考虑为 buffers 编写特定于应用程序的自定义验证的例程。谷歌的一些工 程师得出的结论是,使用 required 弊大于利;他们更喜欢只使用 optional 和 repeated。但是, 这种观点并未普及。
Reserved保留字段
-
如果你通过完全删除或注释来更新message类型,若未来的一些用户在做更新或修改时就可能再次使用到这些编号如果以后加载相同
.proto
的旧版本,这可能会导致一些严重问题,包括数据损坏,隐私错误等。确保不会发生这种情况的一种方法是指定已删除字段的字段编号(有时也需要指定名称为保留状态,英文名称可能会导致 JSON 序列化问题)为 “保留” 状态。如果将来的任何用户尝试使用这些字段标识符,protocol buffer 编译器将会抱怨。message Foo { reserved 2, 15, 9 to 11; reserved "foo", "bar"; }
导入定义 importing definitions
-
若想要使用另外一个
.proto
文件中的message定义,可以通过在文件顶部添加一个import
语句来对文件进行导入。import "myproject/other_protos.proto";
-
默认情况下,你只能使用直接导入的 .proto 文件中的定义。但是,有时你可能需要将 .proto 文件移动到新位置。现在,你可以在旧位置放置一个虚拟 .proto 文件,以使用 import public 概念将所有导入转发到新位置,而不是直接移动 .proto 文件并在一次更改中更新所有调用点。导入包含 import public 语句的 proto 的任何人都可以传递依赖导入公共依赖项。
// new.proto // All definitions are moved here // old.proto // This is the proto that all clients are importing. import public "new.proto"; import "other.proto"; // client.proto import "old.proto"; // 你可以使用 old.proto 和 new.proto 中的定义,但无法使用 other.proto
更新 message 类型规则
- 请勿更改任何现有字段的字段编号;
- 新添加的任何字段都应该是 optional 或 repeated 字段;
- 只要在更新的 message 类型中不再使用字段编号,就可以删除非必填字段;
- int32,uint32,int64,uint64 和 bool 都是兼容的 。 这意味着你可以将字段从这些类型更改为另一种类型,而不会破坏向前或向后兼容性;
- sint32 和 sint64 彼此兼容,但与其他整数类型不兼容;
- 只要字节是有效的 UTF-8,string 和 bytes 就是兼容的;
- 如果字节包含 message 的编码版本,则嵌入 message 与 bytes 兼容;
- fixed32 与 sfixed32 兼容,fixed64 与 sfixed64 兼容;
- optional 与 repeated 兼容;
- 更改默认值通常是正常的,只要你记住永远不会通过网络发送默认值。因此,如果程序接收到未设置特定字段的消息,则程序将看到该程序的协议版本中定义的默认值。它不会看到发件人代码中定义的默认值;
- enum 与 int32,uint32,int64 和 uint64兼容(注意,如果它们不适合,值将被截断),但要注意 message 反序列化时客户端代码对待它们将有所不同。值得注意的是,当 message 被反序列化时,将丢弃无法识别的 enum 值,这使得字段的 has… 访问器返回 false 并且其 getter 返回 enum 定义中列出的第一个值,或者如果指定了一个默认值则返回默认值。在 repeated 枚举字段的情况下,任何无法识别的值都将从列表中删除。但是,整数字段将始终保留其值。因此,在有可能接收超出范围的枚举值时,对整数升级为 enum 这一操作需要非常小心;
- 将单个 optional 值更改为 newoneof 的成员是安全且二进制兼容的。如果你确定没有代码一次设置多个,则将多个 optional 字段移动到新的 oneof 中可能是安全的。但是将任何字段移动到现有的 oneof 是不安全的。
定义一个protocol buffer message 类型
//简易的博客系统定义proto字段
/*
*1.文章属性:ID、标题、描述、正文、标签、是否指定、创建时间、创建作者
*2.标签类别:最热、PHP、最新
*3.作者属性:ID、名称、描述
*/
message BlogSystem{
enum TagType{
HOT = 1;
PHP = 2;
NEW = 3;
}
message AuthorMessage{
required int32 id = 1;
required string name = 2;
optional string introduce = 3;
}
required int32 id = 1;
required string title = 2;
optional string description = 3;
required string content = 4;
required TagType tag = 5;
optional bool stick = 6;
required string createtime = 7;
required AuthorMessage author = 8;
}