简介
Protocol Buffer(Protobuf)
: 轻便高效的结构化数据存储格式(类似于xml,json),可以用于结构化数据串行化,很适合做数据存储或PRC数据交换格式。- 可用于通讯协议,数据存储等领域的语言无关,平台无关,可扩展的序列化结构数据格式。
- 可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。
- 现在可以用
proto3
或者proto2
,推荐用proto3
如何工作
指定protobuf
版本,
syntax = "proto3" # 如果没有指定默认是proto2
- 首先需要一个
.proto
文件中定义需要串行化的数据结构信息。每个ProtocolBuffer
信息是一小段逻辑记录,包含一系列的键值对。 - 在
protobuf
术语中,结构化数据被称为Message
.
# 一个好的习惯的proto文件的命名为 packageName.MessageName.proto
# message可以编译成对应的对象,eg. java的class go的struct
package lm;
message Persion {
required string name=1;
required int32 id=2;
optional string email=3; # 可选的
repeated int32 samples = 4[packed=true]; # repeated可以重复任意多次 他会编译成数组
enum PhoneType {
MOBILE=0;
HOME=1;
WORK=2;
}
message PhoneNumber { # 可以嵌套
required string number=1;
//...
}
}
- 同一个message的每个字段都有唯一一个编号,并且建议终生这个编号都不要改变。
引入其他的proto文件
import "other.proto";
import public "orther2.proto"; # public具有传递性,如果other2也引入了第三方proto文件,那么你这个文件同时也会引入第三方proto
package
package foo.bar;
option
option
可以用在proto
,message
,enum
,service
的scope中- option的定义格式
option optionname = constant;
定义数据类型
- 指定字段类型
- 分配标识号:在消息定义中,每一个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。
[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。
- 在一个
.proto
文件中可以定义多个消息类型。 - 写好了
proto
文件之后通过编译,就可以把这个文件编译成目标语言了。然后就可以通过set/get来设置或者访问里面的字段。 - 然后就可以调用这个编译成的类/结构体进行通信/序列化。
Oneof
- 同时最多允许这一组中的一个字段出现,但是Oneof允许你设置零值。
- proto3没有办法区分正常的值是否是设置了还是取得缺省值(比如int64类型字段,如果它的值是0,你无法判断数据是否包含这个字段,因为0极可能是数据中设置的值,也可能是这个字段的零值),所以你可以通过Oneof取得这个功能,因为Oneof有判断字段是否设置的功能。·
syntax = "proto3";
package abc;
message OneofMessage {
oneof test_oneof {
string name = 4;
int64 value = 9;
}
}
Reserved
- Reserved可以用来指明此message不使用某些字段,也就是忽略这些字段。也可以通过字段编号范围或者字段名称指定保留的字段, 声明保留的字段你就不要再定义了,否则编译的时候会出错。
syntax = "proto3";
package abc;
message AllNormalypes {
reserved 2, 4 to 6;
reserved "field14", "field11";
double field1 = 1;
// float field2 = 2;
int32 field3 = 3;
// int64 field4 = 4;
// uint32 field5 = 5;
// uint64 field6 = 6;
sint32 field7 = 7;
sint64 field8 = 8;
fixed32 field9 = 9;
fixed64 field10 = 10;
// sfixed32 field11 = 11;
sfixed64 field12 = 12;
bool field13 = 13;
// string field14 = 14;
bytes field15 = 15;
}
枚举类型
- 枚举值是枚举类型的兄弟类型不是子类型,所以在同一个package下不能定义重名的枚举字段
enum EnumAllowingAlias {
option allow_alias = true; // allow_alias可以运行字段编号重复, running是started的别名
UNKNOWN = 0; //第一个枚举值必须是0,而且必须定义。
STARTED = 1;
RUNNING = 1;
}
enum EnumNotAllowingAlias {
UNKNOWN2 = 0;
STARTED2 = 1;
// RUNNING = 1;
}
更新消息类型
- 有时候你不得不修改正在使用的proto文件,比如为类型增加一个字段,protobuf支持这种修改而不影响已有的服务,但是你需要遵循一定的规则:
- 不要改变已有字段的字段编号
- 当你增加一个新的字段的时候,老系统序列化后的数据依然可以被你的新的格式所解析,只不过你需要处理新加字段的缺省值。 老系统也能解析你信息的值,新加字段只不过被丢弃了
- 字段也可以被移除,但是建议你Reserved这个字段,避免将来会使用这个字段
- int32, uint32, int64, uint64 和 bool类型都是兼容的
- sint32 和 sint64兼容,但是不和其它整数类型兼容
- string 和 bytes兼容,前提是: bytes 是合法的UTF-8 bytes
- 嵌入类型和bytes兼容,前提是:bytes包含一个消息的编码版本
- fixed32和sfixed32, fixed64和sfixed64,enum和int32, uint32, int64, uint64格式兼容
- 把单一一个值改变成一个新的oneof类型的一个成员是安全和二进制兼容的。把一组字段变成一个新的oneof字段也是安全的,如果你确保这一组字段最多只会设置一个。把一个字段移动到一个已存在的oneof字段是不安全的
优点
- 主要优点就是快和简单,而且占用更小,
- 向后兼容性好,人们不必破坏已经部署的,依靠老数据格式的程序就可以对数据结构进行升级,这样不用担心因为消息结构的改变造成的大规模的代码重构或者迁移问题。