概述
它类似于 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 允许无缝支持更改,包括在不中断现有服务的情况下,向任何 protocol buffer 添加新字段和删除现有字段。有关此主题的更多信息,请参阅本主题稍后的 无需更新代码即可更新 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 和类似格式的开销更少。
- Protocol buffers 在科学计算中流行的非面向对象语言(如 Fortran 和 IDL)中没有得到很好的支持。
- 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 中,您还可以指定字段是否为可选字段。在 proto3 中,将字段设置为可选字段会将其从隐式存在更改为显式存在。
设置字段的基数后,您需要指定数据类型。Protocol buffers 支持常用的原始数据类型,例如整数、布尔值和浮点数。有关完整列表,请参阅 标量值类型。
字段也可以是
message
类型,以便您可以嵌套定义的各个部分,例如重复的数据集。enum
类型,以便您可以指定一组可供选择的值。oneof
类型,当消息具有许多可选字段且最多同时设置一个字段时,可以使用该类型。map
类型,用于向定义中添加键值对。
消息可以允许扩展在消息本身之外定义字段。例如,protobuf 库的内部消息模式允许扩展自定义的、特定于用途的选项。
有关可用选项的更多信息,请参阅 proto2、proto3 或 2023 版本的语言指南。
设置基数和数据类型后,您需要为字段选择一个名称。在设置字段名称时,需要记住一些事项
- 有时,在字段名称在生产环境中使用后,更改字段名称可能会很困难,甚至不可能。
- 字段名称不能包含破折号。有关字段名称语法的更多信息,请参阅 消息和字段名称。
- 对重复字段使用复数名称。
为字段分配名称后,您需要分配一个字段编号。字段编号不能重新用于其他用途或重复使用。如果您删除一个字段,则应保留其字段编号,以防止有人意外重复使用该编号。
附加数据类型支持
Protocol buffers 支持多种标量值类型,包括使用可变长度编码和固定大小的整数。您还可以通过定义消息来创建自己的复合数据类型,消息本身是您可以分配给字段的数据类型。除了简单和复合值类型之外,还发布了几个常用类型。
历史
要了解 protocol buffers 项目的历史,请参阅 Protocol Buffers 历史。
Protocol Buffers 开源理念
Protocol buffers 于 2008 年开源,旨在为 Google 外部的开发者提供与我们在内部获得的相同好处。当我们进行这些更改以支持我们的内部需求时,我们会定期更新语言,从而支持开源社区。虽然我们接受来自外部开发者的精选 pull request,但我们并非总是能够优先处理不符合 Google 特定需求的功能请求和错误修复。
开发者社区
要及时了解 Protocol Buffers 的即将到来的更改并与 protobuf 开发者和用户联系,请加入 Google Group。