ProtoBuf 是什么
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化,很适合做数据存储或 RPC 数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式
ProtoBuf 专门用来描述客户端与服务之间交互的数据格式以及消息的解析和序列化,引入之后有以下几个优点:
- 传输效率跟解析效率都比文本序列化格式 (例如:json、xml等) 要高不少,平台无关,语言无关,可扩展的序列化结构数据格式
- 有了统一的语法描述交互的数据结构,就能利用工具很方便的自动化生成 API 文档 (例如:swagger 文档)
- 强制编写 proto 文件 + 自动生成文档,可以很大程度解决客户端跟服务之前文档更新不及时的问题
- Google 做背书,生态完善,各个语言的库都有支持
语法速记
// 版本定义,最新的主要用 proto3
syntax = "proto3";
// 常规用法
import "google/protobuf/any.proto";
// 通过 public 引入第三方的proto文件,那么引入你这个文件同时也会引入第三方的proto
import public "other2.proto";
// 定义proto的包名,包名可以避免对message 类型之间的名字冲突
package foo.bar;
// option的定义格式是 "option" optionName "=" constant ";"
option java_package = "com.example.foo";
message OneofMessage {
// Reserved 可以通过字段编号范围或者字段名称指定保留的字段, 这些字段会被忽略
reserved 2, 4 to 6;
reserved "field14", "field11";
double field1 = 1;
// float field2 = 2;
int32 field3 = 3;
// int64 field4 = 4;
// uint32 field5 = 5;
// uint64 field6 = 6;
sint32 field7 = 7;
sint64 field8 = 8;
fixed32 field9 = 9;
fixed64 field10 = 10;
// sfixed32 field11 = 11;
sfixed64 field12 = 12;
bool field13 = 13;
// string field14 = 14;
bytes field15 = 15;
// 一组字段,同时最多允许这一组中的一个字段
oneof test_oneof {
string name = 16;
int64 value = 17;
}
// map 字段不能同时使用 repeated
map<int64, string> values = 18;
// 消息可以直接内嵌枚举
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
}
Corpus corpus = 19;
// repeated 表示是个列表,可以直接其它类型
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
// 枚举的常量必须是一个32比特的整数,从效率的角度考虑,不推荐采用负数
// 第一个枚举值必须是0,而且必须定义
enum EnumAllowingAlias {
// 设置 allow_alias,允许字段编号重复,RUNNING 是 STARTED 的别名
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
版本迭代注意事项
- 不要改变已有字段的字段编号
- 当你增加一个新的字段的时候,老系统序列化后的数据依然可以被你的新的格式所解析,只不过你需要处理新加字段的缺省值。 老系统也能解析你信息的值,新加字段只不过被丢弃了
- 字段也可以被移除,但是建议你Reserved这个字段,避免将来会使用这个字段
- int32, uint32, int64, uint64 和 bool类型都是兼容的
- sint32 和 sint64兼容,但是不和其它整数类型兼容
- string 和 bytes兼容,如果 bytes 是合法的UTF-8 bytes的话
- 嵌入类型和bytes兼容,如果bytes包含一个消息的编码版本的话
- fixed32和sfixed32, fixed64和sfixed64
- enum和int32, uint32, int64, uint64格式兼容
- 把单一一个值改变成一个新的oneof类型的一个成员是安全和二进制兼容的。把一组字段变成一个新的oneof字段也是安全的,如果你确保这一组字段最多只会设置一个。把一个字段移动到一个已存在的oneof字段是不安全的
kratos 中的应用
使用 kratos 脚手架新建项目之后,proto 文件都是被整理在根目录下的 api 目录下,我们在api 下创建一个 v1 目录放置第一版本的所有的接口定义,目录结构如下:
└── api
└── v1
└── user.proto
然后编写 user.proto 文件
syntax = "proto3";
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
import "google/protobuf/empty.proto";
package user.v1;
option go_package = "v1";
option (gogoproto.goproto_getters_all) = false;
service user {
rpc mine_info(GetMineInfoReq) returns (GetMineInfoResp);
}
message GetMineInfoReq {
// 用户的id
int64 uid = 1 [(gogoproto.moretags) = 'form:"uid" validate:"required"'];
}
message GetMineInfoResp {
// 用户的账号
string account = 1 [(gogoproto.jsontag) = 'account'];
// 是否完成小b账户认证
bool auth = 2 [(gogoproto.jsontag) = 'auth'];
// 用户头像
string avatar = 3 [(gogoproto.jsontag) = 'avatar'];
// 用户名
string name = 4 [(gogoproto.jsontag) = 'name'];
}
编写完成后在 v1 目录下使用命令 kratos tool protoc user.proto
来默认执行所有类型文件的生成,比如:api.pb.go/api.bm.go/api.swagger.json/api.ecode.go
的对应文件
但是在执行的时候报错,说有 几个库没有定义,这些都是一些第三方的库,在打印的第一行可以看到 protoc
命令执行的原始命令,可以看到引入的几个库的目录里面确实没有 google/protobuf/descriptor.proto
这个库
[16:50:26] jeff:v1 $ kratos tool protoc user.proto
2020/11/19 16:50:35 protoc --proto_path=/Users/jeff/go/src --proto_path=/Users/jeff/go/pkg/mod/github.com/go-kratos/kratos@v0.5.1-0.20201019061239-217cc8e5e918/third_party --proto_path=/Users/jeff/Desktop/workspace/others/kratos-test/user-service/api/v1 --bm_out=:. user.proto
google/protobuf/descriptor.proto: File not found.
github.com/gogo/protobuf/gogoproto/gogo.proto:32:1: Import "google/protobuf/descriptor.proto" was not found or had errors.
github.com/gogo/protobuf/gogoproto/gogo.proto:38:8: "google.protobuf.EnumOptions" is not defined.
github.com/gogo/protobuf/gogoproto/gogo.proto: "google.protobuf.EnumOptions" is not defined.
... 此处忽略 ...
github.com/gogo/protobuf/gogoproto/gogo.proto: "google.protobuf.FieldOptions" is not defined.
google/protobuf/empty.proto: File not found.
user.proto:3:1: Import "github.com/gogo/protobuf/gogoproto/gogo.proto" was not found or had errors.
user.proto:4:1: Import "google/protobuf/empty.proto" was not found or had errors.
exit status 1
exit status 1
装了 protobuf 的库之后是在 /usr/local/include/
这个目录下有这个库
[17:20:14] jeff:v1 $ ll /usr/local/include | grep google
lrwxr-xr-x 1 jeff admin 39B Sep 9 2019 google -> ../Cellar/protobuf/3.7.1/include/google
[17:20:14] jeff:v1 $
[17:20:45] jeff:v1 $ kratos tool protoc user.proto --proto_path=/usr/local/include/
2020/11/19 17:20:47 protoc --proto_path=/Users/jeff/go/src --proto_path=/Users/jeff/go/pkg/mod/github.com/go-kratos/kratos@v0.5.1-0.20201019061239-217cc8e5e918/third_party --proto_path=/Users/jeff/Desktop/workspace/others/kratos-test/user-service/api/v1 --bm_out=:. user.proto --proto_path=/usr/local/include/
user.proto:4:1: warning: Import google/protobuf/empty.proto is unused.
2020/11/19 17:20:47 protoc --proto_path=/Users/jeff/go/src --proto_path=/Users/jeff/go/pkg/mod/github.com/go-kratos/kratos@v0.5.1-0.20201019061239-217cc8e5e918/third_party --proto_path=/Users/jeff/Desktop/workspace/others/kratos-test/user-service/api/v1 --gofast_out=plugins=grpc:. user.proto --proto_path=/usr/local/include/
user.proto:4:1: warning: Import google/protobuf/empty.proto is unused.
2020/11/19 17:20:47 protoc --proto_path=/Users/jeff/go/src --proto_path=/Users/jeff/go/pkg/mod/github.com/go-kratos/kratos@v0.5.1-0.20201019061239-217cc8e5e918/third_party --proto_path=/Users/jeff/Desktop/workspace/others/kratos-test/user-service/api/v1 --bswagger_out=:. user.proto --proto_path=/usr/local/include/
user.proto:4:1: warning: Import google/protobuf/empty.proto is unused.
2020/11/19 17:20:47 protoc --proto_path=/Users/jeff/go/src --proto_path=/Users/jeff/go/pkg/mod/github.com/go-kratos/kratos@v0.5.1-0.20201019061239-217cc8e5e918/third_party --proto_path=/Users/jeff/Desktop/workspace/others/kratos-test/user-service/api/v1 --ecode_out=:. user.proto --proto_path=/usr/local/include/
user.proto:4:1: warning: Import google/protobuf/empty.proto is unused.
2020/11/19 17:20:47 generate user.proto --proto_path=/usr/local/include/ success.
gogoprotobuf
go 中使用 protobuf 还有一个选择就是 gogoprotobuf,gogoprotobuf 除了兼容 protobuf 之外,增加了很多 option,所以可以提供更低颗粒度的控制,生成代码更高效也更合理,比如etcd、k8s、dgraph、docker swarmkit都使用它,而 kratos 使用的也是这个库
gogoprotobuf 增加了很多的不同层级的 option,详细可以参考 gogoproto extensions
option name | option | 描述 |
---|---|---|
gogoproto.goproto_enum_prefix | Enum | 选项为false时,则生成的代码中不加"E_" |
gogoproto.goproto_getters | Message | 选项为false时,则不会为message的每个field生成一个Get函数 |
gogoproto.face | Message | 选项为true时,会为message生成相应的interface |
gogoproto.nullable | Field | 选项为false时,message序列化的时候,gogo会为message的每个field设置一个值 |
gogoproto.goproto_stringer | Message | 选项为false时,gogo不再为message对一个的struct生成String()方法 |
goland 使用 Protobuf Support 时,引入第三方库找不到的问题
goland 安装插件 Protobuf Support
之后,引入比如 gogo 的库,会产生报错,提示 File not found
,对于使用其实没什么影响,就是写的时候不是很美观,另外对于 option 是否有拼写错误也失去了静态检查
这个问题主要是因为插件的库里面并没有我们需要的第三方的库,所以才会找不到,而 google/protobuf/empty.proto
这个库不报错就是因为插件里面集成了这个库,按着 command 键可以看到,这个库的地址是在 /Users/jeff/Library/Application Support/JetBrains/GoLand2020.2/plugins/protobuf-jetbrains-plugin/lib/protobuf-java-3.6.1-sources.jar
目录下
这后面其实就很简单了,利用 unzip
以及 jar
命令可以完成 jar包的解包跟打包,把 gogoprotobuf 库相关的都放到里面去,然后替换之前的库的 jar 包即可
替换之后报错会直接消失,并且可以通过 command 键追溯到库源码