RPC中protocol的使用 | 青训营

169 阅读5分钟

protobuf介绍

Protobuf全称Protocol Buffer,是 Google 公司于2008年开源的一种语言无关、平台无关、可扩展的用于序列化结构化数据——类似于XML,但比XML更小、更快、更简单,它可用于(数据)通信协议、数据存储等。你只需要定义一次你想要的数据结构,然后你就可以使用特殊生成的源代码来轻松地从各种数据流和各种语言中写入和读取你的结构化数据。目前 Protobuf 被广泛用作微服务中的通信协议。 protocol buffer编译器需要一个插件来根据提供的proto文件生成 Go 代码,Go1.16+请使用下面的命令安装插件。

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

这个命令将在 $GOBIN 中安装一个 protocol-gen-go 的二进制文件。我们需要确保 $GOBIN 在你的环境变量中,protocol buffer编译器才能找到它(可以通过go env命令查看$GOPATH)。

当使用go_out 标志调用 protoc 时,protocol buffer编译器将生成 Go 代码。protocol buffer编译器会将生成的Go代码输出到命令行参数go_out指定的位置。go_out标志的参数是你希望编译器编写 Go 输出的目录。编译器为每个.proto 文件输入创建一个源文件。输出文件的名称是通过将.proto 扩展名替换为.pb.go 而创建的。

生成的.pb.go文件放置的目录取决于编译器标志。有以下几种输出模式:

  • paths=import:输出文件放在以 Go 包的导入路径命名的目录中。例如,protos/buzz.proto文件中带有example.com/project/protos/fizz的导入路径,则输出的生成文件会保存在example.com/project/protos/fizz/buzz.pb.go。如果未指定路径标志,这就是默认输出模式。
  • module=$PREFIX:输出文件放在以 Go 包的导入路径命名的目录中,但是从输出文件名中删除了指定的目录前缀。例如,输入文件 pros/buzz.proto,其导入路径为 example.com/project/protos/fizz 并指定example.com/projectmodule前缀,结果会产生一个名为 pros/fizz/buzz.pb.go 的输出文件。在module路径之外生成任何 Go 包都会导致错误。此模式对于将生成的文件直接输出到 Go 模块非常有用。
  • paths=source_relative:输出文件与输入文件放在相同的相对目录中。例如,一个protos/buzz.proto输入文件会产生一个位于protos/buzz.pb.go的输出文件。

在调用protoc时,通过传递 go_opt 标志来提供特定于 protocol-gen-go 的标志位参数。可以传递多个go_opt标志位参数。例如,当执行下面的命令时:

protoc --proto_path=src --go_out=out --go_opt=paths=source_relative foo.proto bar/baz.proto

编译器将从 src 目录中读取输入文件 foo.proto 和 bar/baz.proto,并将输出文件 foo.pb.go 和 bar/baz.pb.go 写入 out 目录。如果需要,编译器会自动创建嵌套的输出子目录,但不会创建输出目录本身。

package

为了生成 Go 代码,必须为每个 .proto 文件(包括那些被生成的 .proto 文件传递依赖的文件)提供 Go 包的导入路径。有两种方法可以指定 Go 导入路径:

  • 通过在 .proto 文件中声明它。
  • 通过在调用 protoc 时在命令行上声明它。

我们建议在 .proto 文件中声明它,以便 .proto 文件的 Go 包可以与 .proto 文件本身集中标识,并简化调用 protoc 时传递的标志集。 如果给定 .proto 文件的 Go 导入路径由 .proto 文件本身和命令行提供,则后者优先于前者。

Go 导入路径是在 .proto 文件中指定的,通过声明带有 Go 包的完整导入路径的 go_package 选项来创建 proto 文件。用法示例:

option go_package = "example.com/project/protos/fizz";

调用编译器时,可以在命令行上指定 Go 导入路径,方法是传递一个或多个 M${PROTO_FILE}=${GO_IMPORT_PATH} 标志位。用法示例:

protoc --proto_path=src \
  --go_opt=Mprotos/buzz.proto=example.com/project/protos/fizz \
  --go_opt=Mprotos/bar.proto=example.com/project/protos/foo \
  protos/buzz.proto protos/bar.proto

由于所有 .proto 文件到其 Go 导入路径的映射可能非常大,这种指定 Go 导入路径的模式通常由控制整个依赖树的某些构建工具(例如 Bazel)执行。 如果给定的 .proto 文件有重复条目,则指定的最后一个条目优先。

对于 go_package 选项和 M 标志位,它们的值可以包含一个显式的包名称,该名称与导入路径之间用分号分隔。 例如:“example.com/protos/foo;package_name”。 不鼓励这种用法,因为默认情况下包名称将以合理的方式从导入路径派生。

导入路径用于确定一个 .proto 文件导入另一个 .proto 文件时必须生成哪些导入语句。 例如,如果 a.proto导入 b.proto,则生成的 a.pb.go 文件需要导入包含生成的 b.pb.go 文件的 Go 包(除非两个文件在同一个包中)。 导入路径也用于构造输出文件名。 有关详细信息,请参阅上面的“编译器调用”部分。

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

Go语言使用protoc示例

我们新建一个名为demo的项目,并且将项目中定义的.proto文件都保存在proto目录下。

本文后续的操作命令默认都在demo目录下执行。

普通编译

下面的示例中我们将定义一个单独的proto文件并进行编译。

定义proto

新建一个price.proto文件。

// proto/book/price.proto

syntax = "proto3";

package book;

// 声明生成Go代码的导入路径(import path)
option go_package = "github.com/Q1mi/demo/proto/book";

message Price {
    int64 market_price = 1;  // 建议使用下划线的命名方式
    int64 sale_price = 2;
}

我们在这个文件中使用option go_package = "github.com/Q1mi/demo/proto/book"语句声明了生成的Go代码的导入路径。

项目当前的目录结构如下:

demo
└── proto
    └── book
        └── price.proto

生成代码

假设我们想把最终生成的Go代码还保存在proto文件夹中,那么就可以执行下面的命令。

protoc --proto_path=proto --go_out=proto --go_opt=paths=source_relative book/price.proto

其中:

  • --proto_path=proto 表示从proto目录下读取proto文件。
  • --go_out=proto 表示生成的Go代码保存的路径。
  • --go_opt=paths=source_relative 表示输出文件与输入文件放在相同的相对目录中。
  • book/price.proto 表示在proto目录下的book/price.proto文件。

此外,--proto_path有一个别名-I,上述编译命令也可以这样写。

protoc -I=proto --go_out=proto --go_opt=paths=source_relative book/price.proto

执行上述命令将会在proto目录下生成book/price.pb.go文件。

demo
└── proto
    └── book
        ├── price.pb.go
        └── price.proto

此处如果不指定--proto_path参数那么编译命令可以简写为:

protoc --go_out=. --go_opt=paths=source_relative proto/book/price.proto

上面的命令都是将代码生成到demo/proto目录,如果想要将生成的Go代码保存在其他文件夹中(例如pb文件夹),那么我们需要先在demo目录下创建一个pb文件夹。然后在命令行通过--go_out=pb指定生成的Go代码保存的路径。完整命令如下:

protoc --proto_path=proto --go_out=pb --go_opt=paths=source_relative book/price.proto

执行上面的命令便会在demo/pb文件夹下生成Go代码。

demo
├── pb
│   └── book
│       └── price.pb.go
└── proto
    └── book
        ├── price.pb.go
        └── price.proto