Protocol Buffers 学习笔记

Protocol Buffers 是一种跨语言、跨平台的序列化方法,可实现结构化数据的高效编码和解码。

概述

Protocol Buffers 是一种数据序列化格式,类似于 JSON,但体积更小、速度更快,并支持本地语言绑定。它允许用户定义数据结构,并自动生成源代码,以便在各种数据流和编程语言中轻松读写结构化数据。

Protocol Buffers 解决了什么问题?

Protocol Buffers 为最大几兆字节的类型化、结构化数据包提供一种序列化格式。该格式适用于短暂的网络传输和长期数据存储。它支持在不影响现有数据或更新代码的情况下扩展新信息,广泛应用于谷歌的服务器间通信和数据存储。它还能保持向后兼容性,支持添加新字段和删除现有字段。

使用 Protocol Buffers 有什么好处?

Protocol Buffers 非常适合需要以语言无关、平台无关、可扩展的方式序列化结构化、类似记录的类型化数据的任何场景。它们通常用于定义通信协议(与 gRPC 一起使用)以及数据存储。

使用 Protocol Buffers 的一些优点

  • 紧凑的数据存储
  • 快速解析
  • 多种编程语言的支持
  • 通过自动生成的类优化功能

跨语言兼容性

同样的消息可以由任何支持的编程语言编写的代码读取。可以在一个平台上使用 Java 程序从一个软件系统捕获数据,根据 .proto 定义序列化它,然后在另一个平台上运行的单独的 Python 应用程序中从序列化数据中提取特定值。

Protocol buffers 编译器 protoc 直接支持以下语言:

以下语言由 Google 支持,但项目源代码位于 GitHub 仓库。protoc 编译器为这些语言使用插件:

其他语言不直接由 Google 支持,而是由其他 GitHub 项目支持。这些语言在协议缓冲区的第三方插件中有介绍。

跨项目支持

可以通过在 .proto 文件中定义消息类型并将其放置在特定项目代码库之外来在项目之间使用 Protocol buffers。如果你正在定义消息类型或枚举,并预计你的团队之外的人员将广泛使用它们,那么可以将它们放在没有依赖关系的单独文件中。
谷歌内部广泛使用的一些 proto 定义示例包括 timestamp.protostatus.proto

Go 生成代码指南

编译器调用

Protocol buffer 编译器需要一个插件来生成 Go 代码。使用 Go 1.16 或更高版本运行以下命令来安装它:

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

定义 proto 文件

创建如下目录结构和定义一个用于测试的 proto 文件:

.
└── proto
└── demo.proto
syntax = "proto3";

message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}

定义好测试用的消息后,为了生成 GO 代码,还必须为每个 .proto 文件提供 Go 包的导入路径。有两种方式指定Go导入路径:

NOTE: 导入路径就是生成的 Go 代码的文件路径。

  1. .proto 文件中声明它
  2. 在调用 protoc 时在命令行上声明它

NOTE: 通常建议在 .proto 文件中声明它,以便通过 .proto 文件本身来集中识别,并简化调用 protoc 时传递的标志集。

1. 在 .proto 文件中声明

在文件中通过 go_package 选项声明:

syntax = "proto3";
option go_package = "proto/demo";

message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}

执行 protoc --proto_path=proto --go_out=. demo.proto 可以看到成功生成了代码:

.
└── proto
├── demo
│   └── demo.pb.go
└── demo.proto
2. 在调用 protoc 时在命令行上声明

传递一个或多个 M${PROTO_FILE}=${GO_IMPORT_PATH} 标志在命令行上指定。这个例子中通过 --go_opt=Mdemo.proto=proto/demo 声明。

protoc --proto_path=proto --go_out=. --go_opt=Mdemo.proto=proto/demo demo.proto

NOTE: Go 导入路径和 .proto 文件中的包说明符 package 之间没有关联。后者仅与 protobuf 命名空间相关,而前者仅与 Go 命名空间相关。此外,Go 导入路径和 .proto 导入路径之间没有关联。

生成 GO 代码

使用 protoc 命令生成,如:

protoc --proto_path=proto --go_out=. --go_opt=paths=source_relative demo.proto
  • proto_path:指定在编译 .proto 文件时查找导入文件的路径
  • go_out:生成的 Go 代码的导入路径
  • go_opt:用来指定生成的 Go 代码的一些特性,比如是否生成 gRPC 代码、是否使用快速的路径解析器等
常用的 go_opt 选项
  1. paths=source_relative: 表示生成的 Go 代码的导入路径应该相对于 .proto 文件的源文件路径(即 .proto 文件的相对路径),而不是相对于 GOPATH
  2. M<proto_import_path>=<go_import_path>: 映射 .proto 文件中的导入路径到 Go 代码中的导入路径。这在处理第三方 .proto 文件时非常有用,特别是当这些文件的导入路径与 Go 代码的导入路径不匹配时。

参考资料