ProtoBuf(Protocol Buffers)简介
RPC
- [1] gRPC简介2024年5月5日
- [2] gRPC 核心概念和生命周期2024年5月5日
- [3] ProtoBuf(Protocol Buffers)简介2024年5月5日
- [4] ProtoBuf(Protocol Buffers)历史2024年5月5日
概览
Protocol Buffers 是一种语言中立、平台中立的用于序列化结构化数据的可扩展机制。
它类似于JSON,但更小、更快,并生成本地语言绑定。你只需定义一次数据结构,就可以在各种编程语言中使用生成的特殊源代码轻松地将结构化数据写入和读取到各种数据流中。
Protocol buffers 由定义语言(在 .proto
文件中创建)、proto 编译器生成的用于数据交互的代码、语言特定的运行时库以及写入文件(或通过网络发送)的数据序列化格式组成。
Protocol Buffers 解决了什么问题?
Protocol buffers 为多达几兆字节大小的类型化结构化数据的数据包提供了序列化格式。该格式既适用于短期的网络流量,又适用于长期的数据存储。 Protocol buffers 可以在不影响现有数据,不更新代码的情况下添加新信息来实现扩展。
Protocol buffers 是 Google 最常用的数据格式。它们广泛用于服务器间通信以及在磁盘上对数据进行归档。工程师编写的 .proto
文件描述了 protocol buffers 消息和服务。以下是一个示例消息(message
):
message Person {
optional string name = 1;
optional int32 id = 2;
optional string email = 3;
}
Proto 编译器在构建时调用 .proto
文件以生成各种编程语言的代码,以操纵相应的 proto 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 进行无缝更改,包括添加新字段和删除现有字段,不会破坏现有服务。
使用 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
类型或 enum
枚举类型,那么你可以将它们放在自己的文件中,不依赖于其他文件。
在 Google 内部广泛使用的一些跨项目 proto 定义的例子是 timestamp.proto
和 status.proto
。
更新 Proto 定义而无需更新代码
软件产品具有向后兼容性是标准操作,但向前兼容性则不太常见。只要你在更新 .proto
定义时遵循一些简单的实践,旧代码就可以读取新消息而不会出现问题,忽略任何新添加的字段。对于旧代码来说,已删除字段将具有其默认值,并且已删除的重复字段将为空。
有关“重复”字段是什么,参见后文。
新代码也将透明地读取旧消息。旧消息中没有新字段;在这些情况下,protocol buffers 会为其提供一个合理的默认值。
什么时候不适合使用 Protocol Buffers?
Protocol buffers 不适用于所有数据。特别是:
- Protocol buffers 倾向于认为整个消息可以一次加载到内存中,并且不大于对象图。对于超过几兆字节的数据,考虑使用其他解决方案;当处理较大数据时,由于序列化副本的存在,你可能会生成数据的多个副本,这可能会导致内存使用量出现意外的峰值。
- 当 protocol buffers 被序列化时,相同的数据可能会有许多不同的二进制序列化形式。在完全解析它们之前,你无法比较两个消息是否相等。
- 消息没有压缩。虽然消息可以像任何其他文件一样进行 zipped 或 gzipped 处理,但特定用途的压缩算法(如 JPEG 和 PNG 使用的算法)才能为适当类型的数据产生更小的文件。
- 对于涉及大型、多维浮点数数组的许多科学和工程应用程序,protocol buffers 的效率不够高。对于这些应用程序,像 FITS 之类格式具有更少的开销。
- Protocol buffers 在科学计算中流行的非面向对象语言(如 Fortran 和 IDL )中支持不足。
- Protocol buffers 消息不会自描述其数据,但它们具有完全反射的 schema。你可以使用该 schema 来实现自描述。也就是说,你在没有相应的
.proto
文件的情况下无法完全解释一个消息。 - Protocol buffers 不是任何组织的正式标准。这使得它们不适合在需要构建在标准之上的法律或其他要求的环境中使用。
谁在使用 Protocol Buffers?
许多项目使用 protocol buffers,包括以下几个:
Protocol Buffers 工作原理
下图显示了你如何使用协议缓冲区处理数据。
图表 1. Protocol buffers 工作流,来自 https://protobuf.dev/
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 Buffers 定义语法
在定义 .proto
文件时,你可以指定字段是 optional
(可选的)还是 repeated
(重复的)(proto2 和 proto3),或者在 proto3 中将其设置为默认的隐式存在性(proto3 中不存在将字段设置为必需的选项,而在 proto2 中则强烈不建议)。
在设置字段的可选性/重复性之后,你指定数据类型。Protocol buffers 支持常见的基础数据类型,如整数、布尔值和浮点数。有关完整列表,请参阅标量值类型。
除了基础类型,字段还可以是一下类型:
message
消息类型,以便嵌套定义,用于部分数据的重复字段等。enum
枚举类型,以便你指定可供选择的一组值。oneof
类型,当消息具有许多可选字段并且最多同时设置一个字段时,可以使用它。map
映射类型,用于将键值对添加到定义中。
在 proto2 中,消息可以允许extensions(拓展)消息之外定义字段。例如,protobuf 库的内部消息 schema 允许为自定义的特定用途选项添加扩展。
有关可用选项的更多信息,请参阅 proto2 或 proto3 的语言指南。
在设置可选性和字段类型之后,需要为字段选择一个名称。在设置字段名称时需要记住一些事项:
- 字段在生产环境中使用后更改字段名称有时可能会很困难,甚至不可能。
- 字段名称不能包含短横(
-
)。有关字段名称语法的更多信息,请参阅消息和字段名称。 - 对于重复字段,请使用复数形式的名称。
在为字段分配名称之后,则需要分配一个字段编号。字段编号不能改用于其他用途。如果删除了一个字段,则应保留其字段编号,以防止有人意外地重用该编号。
其他数据类型支持
Protocol buffers 支持许多标量值类型,包括使用可变长度编码和固定大小的整数。你还可以通过定义消息来创建自己的复合数据类型,这些消息本身是可以分配给字段的数据类型。除了简单和复合值类型之外,还发布了几种常见类型。
Protocol Buffers 开源哲学
Protocol buffers 于2008年开源,为 Google 之外的开发人员提供与我们内部运作使用提供相同的便利。Google 因内部需求对其作出更改后,也会定期更新语言来同步支持到开源社区。虽然 Google 会接受来自外部开发人员的特定 pull requests,但它不会优先考虑不符合 Google 需求的功能请求和错误修复。