背景

Protobuf 是 google 开源的高性能序列化库,支持 C++、C#、Dart、Java、Go、Python、Rust 等语言,同时也是跨平台的。

Protobuf 包含序列化格式的定义、各种语言的库以及一个 IDL 编译器。正常情况下你需要定义 proto 文件,然后使用 IDL 编译器编译成你需要的语言。

关于什么是 IDL 编译器,请继续往下看。

什么是 protoc

protoc 就是上面提到的 IDL 编译器,也叫 Google protocol buffers compiler,可以把 proto 文件生成成指定语言的序列化代码。

如何安装

Mac 环境可以使用 brew 安装:

1
2
3
brew install protobuf
protoc --version                          
// libprotoc 3.14.0

插件安装

proto 需要一个插件来生成 Go 代码,通过以下方式安装:

1
go get -u -v google.golang.org/protobuf/cmd/protoc-gen-go

安装 grpc 相关插件:

1
go get -u -v google.golang.org/grpc/cmd/protoc-gen-go-grpc

安装 grpc-gateway 相关插件:

1
2
go get -u -v github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway 
go get -u -v github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2

安装 gogo/protobuf 相关插件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 默认
go get -u -v github.com/gogo/protobuf/protoc-gen-gofast

// 想要更快的序列化编码速度的话:
// same as gofast, but imports gogoprotobuf
go get -u -v github.com/gogo/protobuf/protoc-gen-gogofast
// same as gogofast, without XXX_unrecognized, less pointer fields
go get -u -v github.com/gogo/protobuf/protoc-gen-gogofaster
// same as gogofaster, but with generated string, gostring and equal methods
go get -u -v github.com/gogo/protobuf/protoc-gen-gogoslick

基本用法

protoc 常用参数:

1
2
3
-I--proto_path: 指定进行搜索依赖包的目录可以指定多个
--go_out: 指定输出 Go 代码的目录默认为当前目录
--go_opt: 参数为 module 或 paths下面有详细说明

这里假设 proto 文件里面定义的 go_package 为:

1
option go_package = "example.com/foo/bar";

输出的 Go 文件所在的目录的取决于 go_package 选项和编译器(protoc)参数。

默认情况

默认情况生成的代码文件输出位置在 go_package 指定的导入目录。

比如我们有上面 go_package 配置的 protos/foo.proto,那么通过下面得到的文件为 example.com/foo/bar/foo.pb.go

1
protoc --go_out=. protos/foo.proto
指定 –go_opt=module=$PREFIX

如果在使用 protoc 时指定 --go_opt=module=$PREFIX 参数,则在生成代码文件时会忽略 $PREFIX 前缀。

比如我们有上面 go_package 配置的 protos/foo.proto,并指定 --go_opt=module=example.com/foo 参数,那么生成的代码为bar/foo.pb.go, 可以看出 example.com/foo 前缀被去掉了。

1
protoc --go_out=. --go_opt=module=example.com/foo protos/foo.proto
指定 –go_opt=paths=source_relative

如果在使用 protoc 时指定 --go_opt=paths=source_relative 参数(注意 source_relative 是固定写法),则输出文件和输入的 proto 文件在相对的目录,而忽略掉 go_package。

比如我们有上面 go_package 配置的 protos/foo.proto,并指定 --go_opt=paths=source_relative 参数,那么生成的代码为protos/foo.pb.go

1
protoc --go_out=. --go_opt=paths=source_relative protos/foo.proto

上面这个例子可能还不够清晰,我们再举个栗子:

1
protoc --go_out=hello --go_opt=paths=source_relative protos/foo.proto

这时生成的 Go 代码路径为 hello/protos/foo.pb.go,所以这里说的相对目录是指在 --go_out=PATH 前提条件下相对 proto 文件的位置,并不一定和 proto 文件在同一目录下。

最后一个例子

当我们执行如下命令:

1
protoc --proto_path=src --go_out=build/gen --go_opt=paths=source_relative src/foo.proto src/bar/baz.proto

protoc 编译器将读取文件 src/foo.protosrc/bar/baz.proto。它产生两个输出文件:build/gen/foo.pb.gobuild/gen/bar/baz.pb.go

如有必要,编译器会自动创建目录 build/gen/bar,但不会创建 buildbuild/gen,它们必须已经存在,否则会报错。

其他插件

当我们需要生成 grpc 代码时:
1
protoc --go-grpc_out=xxx --go-grpc_opt=xxx proto文件

也就是新增了 --go-grpc_out--go-grpc_opt 参数,这是 protoc-gen-go-grpc 插件实现的功能。

当我们需要生成 grpc-gateway 代码时:

我们看一个官方的例子:

1
2
protoc -I . --grpc-gateway_out ./gen/go --grpc-gateway_opt logtostderr=true \
     --grpc-gateway_opt paths=source_relative  your/service/v1/your_service.proto

如果需要生成 OpenAPI 则需要使用到 protoc-gen-openapiv2 插件,我们看个官方的例子:

1
protoc -I . --openapiv2_out ./gen/openapiv2 --openapiv2_opt logtostderr=true your/service/v1/your_service.proto

关于 grpc 相关的代码生成,其实分三种情况,官方也给出了详细的说明示例,可以戳链接查看。

当我们需要生成 gogo/protobuf 代码时:
1
protoc --gofast_out=. myproto.proto

这里可以看出,只是将原来的 --go_out 参数替换为 --gofast_out就可以享受 gogo/protobuf 带来的性能提高。

当然,如果还想加速性能的话可以使用其他插件,官方提供了另外的几种插件,可以戳链接查看。

参考链接

https://developers.google.com/protocol-buffers/docs/reference/go-generated

https://grpc.io/docs/languages/go/quickstart/

https://github.com/grpc/grpc-go

https://github.com/grpc-ecosystem/grpc-gateway

https://github.com/gogo/protobuf