概览
它类似于 JSON,但更小更快,并且能生成原生语言绑定。你只需一次性定义好你想要的数据结构,然后就可以使用专门生成的源代码,轻松地从各种数据流中读写你的结构化数据,并支持多种语言。
Protocol buffers 结合了定义语言(在 .proto
文件中创建)、proto 编译器生成的用于与数据交互的代码、特定语言的运行时库、写入文件(或通过网络连接发送)的数据序列化格式,以及序列化后的数据。
Protocol Buffers 解决了哪些问题?
Protocol buffers 为大小不超过几兆字节的类型化结构化数据包提供了一种序列化格式。这种格式既适用于临时的网络流量,也适用于长期的数据存储。Protocol buffers 可以在不使现有数据失效或要求更新代码的情况下用新信息进行扩展。
Protocol buffers 是 Google 最常用的数据格式。它们被广泛用于服务器间通信以及磁盘上数据的归档存储。Protocol buffer 的消息和服务由工程师编写的 .proto
文件描述。以下是一个 message
示例:
edition = "2023";
message Person {
string name = 1;
int32 id = 2;
string email = 3;
}
在构建时,proto 编译器会作用于 .proto
文件,以生成各种编程语言的代码(本主题后面跨语言兼容性部分将介绍),用于操作相应的 protocol buffer。每个生成的类都包含每个字段的简单访问器,以及将整个结构序列化为原始字节和从原始字节解析的方法。以下示例展示了如何使用这些生成的方法:
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 允许无缝支持变更,包括添加新字段和删除现有字段,而不会破坏现有服务。有关此主题的更多信息,请参见本主题后面的无需更新代码即可更新 Proto 定义。
使用 Protocol Buffers 有什么好处?
在任何需要以语言中立、平台中立、可扩展的方式序列化结构化的、类似记录的、类型化数据的情况下,Protocol buffers 都是理想的选择。它们最常用于定义通信协议(与 gRPC 一起)和数据存储。
使用 protocol buffers 的一些优点包括:
- 紧凑的数据存储
- 解析速度快
- 支持多种编程语言
- 通过自动生成的类实现优化功能
跨语言兼容性
用任何受支持的编程语言编写的代码都可以读取相同的消息。你可以在一个平台上用一个 Java 程序从一个软件系统中捕获数据,根据 .proto
定义进行序列化,然后在另一个平台上运行的独立 Python 应用程序中从该序列化数据中提取特定值。
以下语言直接在 protocol buffers 编译器 protoc 中得到支持:
以下语言由 Google 支持,但其项目源代码位于 GitHub 仓库中。protoc 编译器对这些语言使用插件:
其他语言并非由 Google 直接支持,而是由其他 GitHub 项目支持。这些语言在Protocol Buffers 的第三方插件中有所介绍。
跨项目支持
你可以通过在特定项目代码库之外的 .proto
文件中定义 message
类型来跨项目使用 protocol buffers。如果你定义的 message
类型或枚举预计将在你的直属团队之外被广泛使用,你可以将它们放在一个没有依赖关系的独立文件中。
在 Google 内部广泛使用的 proto 定义的几个例子是 timestamp.proto
和 status.proto
。
无需更新代码即可更新 Proto 定义
软件产品向后兼容是标准做法,但向前兼容则不太常见。只要你在更新 .proto
定义时遵循一些简单实践,旧代码就能毫无问题地读取新消息,并忽略任何新增的字段。对于旧代码来说,被删除的字段将具有其默认值,而被删除的重复字段将为空。关于“重复”字段的更多信息,请参见本主题后面的Protocol Buffers 定义语法。
新代码也能透明地读取旧消息。新字段在旧消息中不存在;在这种情况下,protocol buffers 会提供一个合理的默认值。
何时 Protocol Buffers 不适用?
Protocol buffers 并不适用于所有数据。特别是:
- Protocol buffers 倾向于假设整个消息可以一次性加载到内存中,并且不大于一个对象图。对于超过几兆字节的数据,应考虑其他解决方案;处理较大数据时,由于序列化副本的存在,你可能最终会得到数据的多个副本,这可能导致内存使用量出现意外的峰值。
- 当 protocol buffers 被序列化时,相同的数据可以有多种不同的二进制序列化形式。在不完全解析的情况下,你无法比较两个消息是否相等。
- 消息不会被压缩。虽然消息可以像其他文件一样进行 zip 或 gzip 压缩,但对于特定类型的数据,像 JPEG 和 PNG 使用的专用压缩算法会产生小得多的文件。
- 对于许多涉及大型、多维浮点数数组的科学和工程用途,Protocol buffer 消息在大小和速度上都并非最高效。对于这些应用,FITS 及类似格式的开销更小。
- 在科学计算中流行的非面向对象语言(如 Fortran 和 IDL)中,Protocol buffers 的支持并不好。
- Protocol buffer 消息本身并不描述其数据,但它们有一个完全自反的模式,你可以用它来实现自描述。也就是说,没有其对应的
.proto
文件,你无法完全解释一个消息。 - Protocol buffers 并非任何组织的正式标准。这使得它们不适合在有法律或其他要求必须基于标准构建的环境中使用。
谁在使用 Protocol Buffers?
许多项目使用 protocol buffers,包括以下项目:
Protocol Buffers 是如何工作的?
下图展示了如何使用 protocol buffers 来处理你的数据。
图 1. Protocol buffers 工作流程
由 protocol buffers 生成的代码提供了实用方法,可以从文件和流中检索数据、从数据中提取单个值、检查数据是否存在、将数据序列化回文件或流,以及其他有用的功能。
以下代码示例展示了在 Java 中此流程的一个例子。如前所示,这是一个 .proto
定义:
message Person {
string name = 1;
int32 id = 2;
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 Buffers 定义语法
在定义 .proto
文件时,你可以指定基数(单一或重复)。在 proto2 和 proto3 中,你还可以指定字段是否为 optional。在 proto3 中,将字段设置为 optional 会将其从隐式存在更改为显式存在。
在设置字段的基数后,你需要指定数据类型。Protocol buffers 支持常见的原始数据类型,如整数、布尔值和浮点数。完整列表请参见标量值类型。
字段的类型也可以是:
- 一个
message
类型,这样你可以嵌套部分定义,例如用于重复的数据集。 - 一个
enum
类型,这样你可以指定一组可供选择的值。 - 一个
oneof
类型,当一个消息有许多可选字段且最多只有一个字段会同时被设置时,你可以使用它。 - 一个
map
类型,用于向你的定义中添加键值对。
消息允许使用扩展来定义消息本身之外的字段。例如,protobuf 库的内部消息模式允许为自定义的、特定于用途的选项提供扩展。
有关可用选项的更多信息,请参阅 proto2、proto3 或 edition 2023 的语言指南。
在设置基数和数据类型之后,你需要为字段选择一个名称。在设置字段名称时,需要注意以下几点:
- 在生产环境中使用后,更改字段名称有时会很困难,甚至不可能。
- 字段名称不能包含短横线。有关字段名称语法的更多信息,请参见消息和字段名称。
- 为重复字段使用复数名称。
为字段分配名称后,你需要分配一个字段编号。字段编号不能被重新调整用途或重用。如果你删除了一个字段,你应该保留其字段编号,以防止有人意外地重用该编号。
额外的数据类型支持
Protocol buffers 支持许多标量值类型,包括使用可变长度编码和固定大小的整数。你还可以通过定义消息来创建自己的复合数据类型,这些消息本身就是可以分配给字段的数据类型。除了简单和复合值类型外,还发布了几种常见类型。
历史
要了解 protocol buffers 项目的历史,请参见Protocol Buffers 的历史。
Protocol Buffers 的开源理念
Protocol buffers 于 2008 年开源,旨在为 Google 以外的开发者提供与我们内部相同的益处。我们通过定期更新语言来支持开源社区,因为我们为了支持内部需求而进行这些更改。虽然我们接受外部开发者的部分拉取请求,但我们不能总是优先处理不符合 Google 特定需求的功能请求和错误修复。
开发者社区
要接收有关 Protocol Buffers 即将发生变化的通知,并与 protobuf 开发者和用户建立联系,请加入 Google 网上论坛。