ProtoBuf(Protocol Buffers)简介

创建于 2024年3月27日修改于 2024年5月5日
ProtoBuf

RPC


概览

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.protostatus.proto

更新 Proto 定义而无需更新代码

软件产品具有向后兼容性是标准操作,但向前兼容性则不太常见。只要你在更新 .proto 定义时遵循一些简单的实践,旧代码就可以读取新消息而不会出现问题,忽略任何新添加的字段。对于旧代码来说,已删除字段将具有其默认值,并且已删除的重复字段将为空。

有关“重复”字段是什么,参见后文。

新代码也将透明地读取旧消息。旧消息中没有新字段;在这些情况下,protocol buffers 会为其提供一个合理的默认值。

什么时候不适合使用 Protocol Buffers?

Protocol buffers 不适用于所有数据。特别是:

谁在使用 Protocol Buffers?

许多项目使用 protocol buffers,包括以下几个:

Protocol Buffers 工作原理

下图显示了你如何使用协议缓冲区处理数据。

Protocol buffers workflow

图表 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 支持常见的基础数据类型,如整数、布尔值和浮点数。有关完整列表,请参阅标量值类型

除了基础类型,字段还可以是一下类型:

在 proto2 中,消息可以允许extensions(拓展)消息之外定义字段。例如,protobuf 库的内部消息 schema 允许为自定义的特定用途选项添加扩展。

有关可用选项的更多信息,请参阅 proto2proto3 的语言指南。

在设置可选性和字段类型之后,需要为字段选择一个名称。在设置字段名称时需要记住一些事项:

在为字段分配名称之后,则需要分配一个字段编号。字段编号不能改用于其他用途。如果删除了一个字段,则应保留其字段编号,以防止有人意外地重用该编号。

其他数据类型支持

Protocol buffers 支持许多标量值类型,包括使用可变长度编码和固定大小的整数。你还可以通过定义消息来创建自己的复合数据类型,这些消息本身是可以分配给字段的数据类型。除了简单和复合值类型之外,还发布了几种常见类型

Protocol Buffers 开源哲学

Protocol buffers 于2008年开源,为 Google 之外的开发人员提供与我们内部运作使用提供相同的便利。Google 因内部需求对其作出更改后,也会定期更新语言来同步支持到开源社区。虽然 Google 会接受来自外部开发人员的特定 pull requests,但它不会优先考虑不符合 Google 需求的功能请求和错误修复。

其他资源