概述

Protocol Buffers 是一种语言中立、平台中立的可扩展机制,用于序列化结构化数据。

它类似于 JSON,但更小、更快,并且生成本机语言绑定。您只需定义一次数据结构,然后可以使用特殊生成的源代码轻松地将结构化数据读写到各种数据流中,并使用各种语言。

Protocol buffers 是定义语言(在 .proto 文件中创建)、proto 编译器生成的用于与数据交互的代码、特定于语言的运行时库、写入文件(或通过网络连接发送)的数据的序列化格式以及序列化数据的组合。

Protocol Buffers 解决什么问题?

Protocol buffers 提供了一种序列化格式,用于打包大小最多几兆字节的类型化结构化数据。该格式适用于短暂的网络流量和长期数据存储。Protocol buffers 可以扩展新的信息,而无需使现有数据无效或需要更新代码。

Protocol buffers 是 Google 最常用的数据格式。它们广泛用于服务器间通信以及磁盘上数据的归档存储。Protocol buffer 的 *消息* 和 *服务* 由工程师编写的 .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("[email protected]")
    .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.protostatus.proto

在不更新代码的情况下更新 Proto 定义

软件产品通常是向后兼容的,但它们向前兼容的情况却较少。只要您在更新 .proto 定义时遵循一些 简单的实践,旧代码将能够读取新消息而不会出现问题,忽略任何新添加的字段。对于旧代码,已删除的字段将具有其默认值,而已删除的重复字段将为空。有关“重复”字段的信息,请参阅本主题后面的 Protocol Buffers 定义语法

新代码也将能够透明地读取旧消息。新字段将不存在于旧消息中;在这些情况下,protocol buffers 将提供合理的默认值。

何时 Protocol Buffers 不适用?

Protocol buffers 并非适用于所有数据。特别是

  • Protocol buffers 倾向于假设整个消息可以一次加载到内存中,并且不会大于对象图。对于超过几兆字节的数据,请考虑其他解决方案;当处理较大的数据时,由于序列化副本,您可能最终会获得数据的多个副本,这会导致内存使用量出现意外的峰值。
  • 当 protocol buffers 被序列化时,相同的数据可以有多种不同的二进制序列化形式。您不能在不完全解析它们的情况下比较两条消息是否相等。
  • 消息未压缩。虽然消息可以像任何其他文件一样被压缩或 gzip 压缩,但像 JPEG 和 PNG 使用的专用压缩算法将为适当类型的数据生成更小的文件。
  • 对于许多涉及大型多维浮点数数组的科学和工程用途,协议缓冲区消息在大小和速度方面都达不到最大效率。对于这些应用程序,FITS 和类似格式的开销更小。
  • Protocol buffers 在科学计算中流行的非面向对象语言(如 Fortran 和 IDL)中不受良好支持。
  • Protocol buffer 消息本身并不描述其数据,但它们具有可用于实现自描述的完全反射模式。也就是说,如果没有访问其相应的 .proto 文件,您就无法完全解释它。
  • Protocol buffers 不是任何组织的正式标准。这使得它们不适合在具有构建在标准之上的法律或其他要求的环境中使用。

谁在使用 Protocol Buffers?

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

Protocol Buffers 如何工作?

下图显示了如何使用 protocol buffers 处理数据。

Compilation workflow showing the creation of a proto file, generated code, and compiled classes
图 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("[email protected]")
    .build();
output = new FileOutputStream(args[0]);
john.writeTo(output);

然后,您可以使用协议缓冲区在其他语言(如 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 中没有设置字段为required的选项,并且在 proto2 中强烈不建议这样做。有关详细信息,请参阅指定字段规则中的“Required is Forever”。)

设置字段的可选性/可重复性后,您需要指定数据类型。协议缓冲区支持常用的基本数据类型,例如整数、布尔值和浮点数。有关完整列表,请参阅标量值类型

字段也可以是

  • message类型,以便您可以嵌套定义的一部分,例如用于重复的数据集。
  • enum类型,以便您可以指定一组可供选择的数值。
  • oneof类型,当消息具有多个可选字段并且同一时间最多只有一个字段会被设置时,可以使用此类型。
  • map类型,以便在定义中添加键值对。

在 proto2 中,消息可以允许扩展来定义消息本身之外的字段。例如,protobuf 库的内部消息模式允许扩展自定义的、特定于用法的选项。

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

设置可选性和字段类型后,您可以为字段选择一个名称。设置字段名称时,需要注意以下几点

  • 在生产环境中使用字段名称后,更改字段名称有时会很困难,甚至不可能。
  • 字段名称不能包含连字符。有关字段名称语法的更多信息,请参阅消息和字段名称
  • 对于重复字段,使用复数名称。

为字段分配名称后,您需要分配一个字段编号。字段编号不能重新分配或重复使用。如果删除了字段,则应保留其字段编号,以防止其他人意外地重新使用该编号。

其他数据类型支持

协议缓冲区支持许多标量值类型,包括使用可变长度编码和固定大小的整数。您还可以通过定义自身为数据类型的消息来创建自己的复合数据类型,您可以将这些数据类型分配给字段。除了简单和复合值类型外,还发布了一些常用类型

历史

要了解协议缓冲区项目的历史,请参阅协议缓冲区历史

Protocol Buffers 开源理念

协议缓冲区于 2008 年开源,旨在为 Google 之外的开发者提供与我们在内部获得的相同好处。当我们进行更改以支持我们的内部需求时,我们会通过定期更新语言来支持开源社区。虽然我们接受来自外部开发者的精选拉取请求,但我们无法始终优先处理不符合 Google 特定需求的功能请求和错误修复。

开发者社区

要及时了解协议缓冲区的即将发布的更改,并与 protobuf 开发人员和用户建立联系,请加入 Google 群组

其他资源