Protocol Buffer
是Google提供的一种数据序列化协议,它是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化,很适合做数据存储或 RPC
数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
相较于XML来说,protobuf
更小更快更简单,支持自定义的数据结构,用 protobuf
编译器生成特定语言的源代码,如 C++、Java、Python、Go等,目前 protobuf
对主流的编程语言都提供了支持,非常方便的进行序列化和反序列化。
Protocol Buffer
序列化之后得到的数据不是可读的字符串,而是二进制流
。通过将结构化的数据(拥有多种属性)进行序列化,从而实现(内存与硬盘之间)数据存储和交换的功能。
- 序列化: 按照
.proto
协议文件将数据结构或对象
转换成二进制流
的过程; - 反序列化:将在序列化过程中所生成的
二进制流
转换成数据结构或对象
的过程。
Protocol Buffer
定义数据格式的文件一般保存在 .proto
文件中,每一个 message
代表了一类结构化的数据,message
里面定义了每一个属性的类型和名字
。
Mac 下安装 Protobuf 工具:
1 | # 安装homebrew |
一、Message 消息结构体
1 | syntax = "proto3"; // 指定proto版本,默认为proto2,而且必须在第一行 |
如上,一个最简单的 .proto
文件 需要包含的几个要素:
- proto版本声明:不写时默认为
proto2
- 包名:package
- message:消息体
对于消息的结构定义:
1、field 编号
- 每个
field
都被分配了一个编号,这个编号是这个field
的唯一标识, - 标识 1-15 在编码的时候只占用一个字节,16-2047占用两个字节,所以为了进一步优化我们的程序,对于那种经常出现的 element,建议使用1-15来作为他们的唯一标识,对那些不被经常使用的 element,建议使用16-2047
- 编号的范围是 1-2^29
- 19000-19999是系统预留的,不要使用
2、field 类型
Message
中 filed
约束类型:
required
:消息体中必填字段,不设置会导致编解码异常。optional
: 消息体中可选字段,可通过default关键字设置默认值。repeated
: 消息体中可重复字段,重复的值的顺序会被保留。其中,proto3
默认使用packed
方式存储,这样编码方式比较节省内存。singular
:一个遵循singular
规则的字段,在一个结构良好的 message 消息体(编码后的message) 可以有0或1个该字段(但是不可以有多个)。这是proto3
语法的默认字段规则。
reserved
:在开发过程中可能会涉及到对proto
文件中message
各个fields
的修改,可能是更新、删除某个field
及其表示,这样可能会导致调用的服务失败。其中一个防止这种问题的方式是,确保你要删除的field
的标识(或是名字)是reserved
,具体protobuf
的编译器会决定未来这个field
表示能否被使用1
2
3
4
5//数字标识和命名不能在同一条语句中混合声明
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
3、多message结构
在一个proto文件中可以定义多个protobuf
4、默认值
Proto Message 各种数据类型与高级编程语言数据类型的对照在本文末附录。
- 对于string和byte类型,默认值为空;
- 对于bool类型,默认值是false;
- 对于数值类型,默认值是0;
- 对于枚举类型,默认值是第一个枚举值,默认为0;
- 对于message类型,默认值由编程语言决定;
- 对于repeated field,默认值为空
5、枚举类型
1 | message Helloworld { |
通常枚举类型的第一个值初始化为0,而且在 message 中使用枚举类型,必须要给定一个为0的值,而且这个为0的值应该为第一个元素。
也可以给不同的元素以相同的alias,但是需要指定 option allow_alias = true;
如下:
1 | enum EnumAllowingAlias { |
除此之外,枚举类型不仅可以定义在 message 内部,也可以定义在 message 外部,而且在不同 message 中可以重用enum。
6、嵌套和引用其他message结构
(1)嵌套结构
1 | message Response { |
(2)引用内部message
1 | message User { |
(3)跨文件引用message
1 | // 被引用的文件 user.proto |
1 | import "user.proto"; // 引入指定文件 |
7、修改 message
当 message 结构因业务变更等原因需要调整时,有如下注意事项
- 不要更改已经存在的
fields
的数字标识 - 如果添加新的
field
,利用旧代码序列化得到的 message 可以使用新的代码进行解析,你需要记住各个元素的默认值。新代码创建的 field 同样可以由旧代码进行加解析 - field 可以被删除,但是需要保证其对应的数字标识不再被使用,你可以通过加前缀的方式来重新使用这个field name,或者指定数字标识为
reserved
来避免这种情况 - int32、int64、uint32、uint64、bool 这些类型都是互相兼容的,并不会影响前向、后向兼容性
- sint32 和 sint64之间是互相兼容的,但是和其他数字类型是不兼容的
- string 和 bytes是互相兼容的,只要使用的是UTF-8编码
- 如果 byte 包含 message 的编码版本,则嵌套的 message 和 bytes 兼容
- flexed32 兼容 sfixed32, fixed64, sfixed64
- enum 兼容 int32, uint32, int64, uint64
二、Service 服务接口
如果想将 Message 用在RPC系统中,需要在 .proto
文件中定义一个 RPC 服务接口,protobuf
编译器会根据所选择语言生成对应语言的服务接口代码及存根。例如定义一个RPC服务并具有一个方法,该方法能够接受 SearchRequest
并返回一个 SearchResponse
,此时就可以在 .proto
文件进行如下定义:
1 | service SearchService { |
gRPC 就是使用 Protobuf 的一个RPC系统,gRPC 在使用 Protobuf 时候非常有效。
三、编译 Proto 文件
1 | protoc --go_out=./ path/filename.proto |
输出文件:filename.pb.go
参考文档:
附:变量类型对照表
.proto Type | C++ Type | Java/Kotlin Type | Python Type | Go Type | Ruby Type | C# Type | PHP Type | Dart Type |
---|---|---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double | float | double |
float | float | float | float | float32 | Float | float | float | double |
int32 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int |
int64 | int64 | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 |
uint32 | uint32 | int[2] | int/long[4] | uint32 | Fixnum or Bignum (as required) | uint | integer | int |
uint64 | uint64 | long[2] | int/long[4] | uint64 | Bignum | ulong | integer/string[6] | Int64 |
sint32 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int |
sint64 | int64 | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 |
fixed32 | uint32 | int[2] | int/long[4] | uint32 | Fixnum or Bignum (as required) | uint | integer | int |
fixed64 | uint64 | long[2] | int/long[4] | uint64 | Bignum | ulong | integer/string[6] | Int64 |
sfixed32 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int |
sfixed64 | int64 | long | int/long[4] | int64 | Bignum | long | integer/string[6] | Int64 |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | bool |
string | string | String | str/unicode[5] | string | String (UTF-8) | string | string | String |
bytes | string | ByteString | str (Python 2) bytes (Python 3) | []byte | String (ASCII-8BIT) | ByteString | string | List |