Protocol Buffers
Protocol Buffers
概览
Protocol buffers提供一个语言中立,平台中立,可扩展的机制,用于向前和向后兼容来序列化结构化数据。它类似于JSON,但是它更小更快,同时它生成原生语言绑定。
Protocol buffers是定义语言(在.proto文件中创建)和proto编译器生成代码的组合。生成代码用于关联数据、语言特定运行时库和序列化格式(写入文件或通过网络连接发送)。
Protocol Buffers解决了什么问题?
Protocol buffer为最多几M字节大小的类型化、结构化数据包提供了一种序列化格式。这种格式既适合短暂的网络传输也适合长时间存储。Protocol buffer可以在不废除已存在数据或更新代码的情况下扩展新信息。
Protocol buffers是Google最广泛使用的数据格式。他们广泛用于内部服务器通信和磁盘数据存档。Protocol buffer messages
和services
由工程师编写的.proto文件描述。下面展示了一个message
的例子:
message Person {
optional string name = 1;
optional int32 id = 2;
optional string email = 3;
}
proto编译器在构建时对.proto文件调用来生成各种编程语言的代码(在稍后的跨语言兼容性中介绍)以操控相应的协议缓冲。每个生成的类包含每个字段的简单访问器,和用以序列化和解析整个结构原始字节的方法。下面展示使用这些生成方法的例子:
Person john = Person.newBuilder()
.setId(1234)
.setName("John Doe")
.setEmail("jdoe@example.com")
.build();
output = new FileOutputStream(args[0]);
john.writeTo(output);
因为protocol buffers在google广泛的跨服务使用,同时它们可能会保持一段时间,保持向后兼容是至关重要的。Protocol buffers允许无缝的支持这些更改,包括向任何协议添加新字段和删除已有字段而不破坏已有的服务。对于该主题的更多信息,查阅下面的在不更新代码情况下更新协议定义
使用Protocol Buffers的好处是什么?
Protocol buffers是任何你需要将结构化、记录化、类型化的数据以语言中立、平台中立,可扩展的方式序列化情况下的理想方案。它们经常被用于定义通信协议(与gRPC一起)或者用于数据存储。
使用protocol buffers的优势包括:
- 紧凑的数据存储
- 快速解析
- 可用于许多编程语言
- 通过自动生成类优化功能。
跨语言兼容性
同样的消息可以被所有支持语言的代码读取。你可以在一个平台上用Java程序从一个软件系统中获取数据,基于一个.proto定义将它序列化,然后在另一个平台上运行的Python应用从序列化数据中取出特定的值。
下面贩语言被protocol buffers的编译器protoc直接支持:
- C++
- C#
- Java
- Kotlin
- Objective-C
- PHP
- Python
- Ruby
下面的语言受google支持,但是项目的源代码存放在GitHub仓库。对下面的语言protoc编译器使用插件:
- Dart
- Go
额外的语言不被Google直接支持,而是由其它GitHub项目支持。这些语言包含在Protocol Buffers的第三方加载项中。
跨项目支持
你可以通过在存放于特定项目外的.proto
文件中定义message
类型来跨项目使用protocol buffers。如果你预计你定义的message
或枚举将会广泛用于外部团队,你可以将它们无依赖的放在它们自己的文件中。
两个google广泛使用的proto定义是timestamp.proto
和status.proto
。
在不更新代码情况下更新协议定义
向后兼容是软件开发的准则,但是向前兼容的情况并不常见。只要在更新.proto定义时遵循一些简单的准则,旧代码读取新消息时就会忽略任何新增的字段而不会产生错误。对旧代码来说,被删除的字段将具有默认值,而被删除的repeated
字段将为空。关于什么是repeated
字段的更多信息,参见稍后的Protocol Buffers定义语法。
新代码也能无感知的读取旧消息。新字段将不会在旧消息中呈现;在这种情况下protocol buffers提供一个合理的默认值。
什么时候不适合用Protocol Buffers?
Protocol Buffers不适合所有数据,例如:
- Protocol Buffers倾向于假设整个消息可以被一次性载入内存并且不大于一个对象图。对于超出几M字节的数据,考虑其它解决方案;当处理更大的数据时,因为序列化副本,很可能最终存在数据的多个副本,这可能会导致内存使用出现惊人的峰值。
- 当protocol buffers序列化之后,相同的数据可能会有不同的二进制序列化值。你不能在没有完全解析它们的情况下对比两个消息是否相等。
- 消息是未压缩的。虽然可以像其它文件一样通过zip或gzip压缩,但是像JPEG和PNG这样为特定目标压缩的算法对于合适的数据类型将会产生更小的文件。
- 对于许多涉及大型多维浮点数组的科学和工程用途,Protocol buffer消息没有达到最优的大小和速度。对于这些应用,FITS和类似格式有更小的开销。
- Protocol buffer对于科学计算流行的非面向对象语言没有很好的支持,例如Fortan和IDL。
- Protocol buffer消息数据不包含自描述,但是他们有完整的反射架构可用于实现自描述。也就是说,你不能在没有获取数据对应.proto文件的情况下完全解释它。
- Protocol buffers不是任何组织的正式标准。这使得它们不适合用于有法律或其它要求为基本标准的环境。
谁在使用Protocol Buffers?
许多外部可用的项目使用protocol buffers,包括下面的:
Protocol Buffers如何工作?
下面的图展示了如何使用protocol buffers处理数据。
图1.Protocol buffers工作流程
protocol buffers生成的代码提供工具方法用于从文件或流中检索数据、从数据中提取单个的值、检查数据是否存在、把数据序列化到文件或流和其它有用的功能。
下面的代码示例展示了在Java中的工作流。如上述,这是一个.proto定义:
message Person {
optional string name = 1;
optional int32 id = 2;
optional string email = 3;
}
编译这个.proto文件产生一个Builder
类,你可以用于创建新实例,如下面Java代码所示:
Person john = Person.newBuilder()
.setId(1234)
.setName("John Doe")
.setEmail("jdoe@example.com")
.build();
output = new FileOutputStream(args[0]);
john.writeTo(output);
然后你可以用protocol buffers生成的其它语言的方法来反序列化数据,例如C++:
Person john;
fstream input(argv[1], ios::in | ios::binary);
john.ParseFromIstream(&input);
int id = john.id();
std::string name = john.name();
std::string email = john.email();
Protocol Buffer定义语法
当定义.proto文件时,你可以指定一个字段是optional
还是repeated
(proto2和proto3)或singular
(proto3)。(设置字段为required
的选项在proto3废弃了,并且在proto2中强烈不建议。关于更多信息,参见字段指定规则的"Required is Forever"。)
当指定了一个字段是可选的或可重复的后,你可以指定数据类型。Protocol buffers支持常用的基本数据类型,例如整型、布尔值、浮点型。全部类型参见标量类型。
一个字段也可以是:
- 一个
message
类型,因此你可以嵌套部分定义,例如用于重复数据集。 - 一个
enum
类型,因此你可以指定一个可选数据集。 - 一个
oneof
类型,可用于消息中含有多个可选字段,并且同时只会有一个字段被设置的情形。 - 一个
map
类型,用来为定义增加键值对。
在proto2,消息允许在消息自身定义外扩展字段。例如,protobuf库的内部消息架构允许扩展自定义的特定用途选项。
更多可用选项的信息参见proto2或proto3的语言指南。
设定了可选性和字段类型后,你需要指定一个字段号。字段号不能修改和重用。如果你删除了一个字段,你需要保留它的字段号来防止其它人意外重用了该数字。
额外的数据类型支持
Protocol buffers支持许多标量值类型,包括同时使用变长编码和定长编码的整型。你也可以定义消息(它们本身就是可以分配给字段的数据类型)来创建组合数据类型。作为简单值类型和复合值类型的补充,还发布了几种常见类型:
常见类型:
- Duration,有符号定长时间段,例如42秒。
- Timestamp,时区和日历无关的时间点,例如2017-01-15T01:30:15.01Z。
- Interval,时区和日历无关的时间区间,例如2017-01-15T01:30:15.01Z - 2017-01-16T02:30:15.01Z。
- Date,完整的日历日期,例如2025-09-19。
- DayOfWeek,一周中的一天,例如Monday。
- TimeOfDay,一天中的时间,例如10:42:23。
- LatLng,经纬度,例如37.386051 latitude and -122.083855 longitude。
- Money,带货币类型的金钱数量,例如42 USD。
- PostalAddress,邮编,例如1600 Amphitheatre Parkway Mountain View, CA 94043 USA。
- Color,RGBA空间颜色。
- Month,一年中的月份,例如April。
Protocol Buffers开源理念
Protocol buffers是在2008年开源的,它为谷歌以外的开发人员提供了与我们内部从它们获得的相同的好处。我们通过对语言的定期更新来支持开源社区,因为我们要做这些更改来支持我们的内部需求。当我们接受来自外部开发人员的选择性pull请求时,我们不能总是优先考虑不符合谷歌特定需求的特性请求和bug修复。