Go Protobuf 简明教程

Golang Protocol Buffers

1 Protocol Buffers 简介

protobuf 即 Protocol Buffers,是一种轻便高效的结构化数据存储格式,与语言、平台无关,可扩展可序列化。protobuf 性能和效率大幅度优于 JSON、XML 等其他的结构化数据格式。protobuf 是以二进制方式存储的,占用空间小,但也带来了可读性差的缺点。protobuf 在通信协议和数据存储等领域应用广泛。例如著名的分布式缓存工具 Memcached 的 Go 语言版本groupcache 就使用了 protobuf 作为其 RPC 数据格式。

.protoprotoc.proto

2 安装

2.1 protoc

从 Protobuf Releases 下载最先版本的发布包安装。如果是 Ubuntu,可以按照如下步骤操作(以3.11.2为例)。

1
2
3
4
# 下载安装包
$ wget https://github.com/protocolbuffers/protobuf/releases/download/v3.11.2/protoc-3.11.2-linux-x86_64.zip
# 解压到 /usr/local 目录下
$ sudo 7z x protoc-3.11.2-linux-x86_64.zip -o/usr/local

如果不想安装在 /usr/local 目录下,可以解压到其他的其他,并把解压路径下的 bin 目录 加入到环境变量即可。

如果能正常显示版本,则表示安装成功。

1
2
$ protoc --version
libprotoc 3.11.2

2.2 protoc-gen-go

.proto
1
go get -u github.com/golang/protobuf/protoc-gen-go
$GOPATH/bin

3 定义消息类型

student.proto
1
2
3
4
5
6
7
8
9
syntax = "proto3";
package main;

// this is a comment
message Student {
string name = 1;
bool male = 2;
repeated int32 scores = 3;
}

在当前目录下执行:

1
2
3
$ protoc --go_out=. *.proto
$ ls
student.pb.go student.proto

即是,将该目录下的所有的 .proto 文件转换为 Go 代码,我们可以看到该目录下多出了一个 Go 文件 student.pb.go。这个文件内部定义了一个结构体 Student,以及相关的方法:

1
2
3
4
5
6
type Student struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Male bool `protobuf:"varint,2,opt,name=male,proto3" json:"male,omitempty"`
Scores []int32 `protobuf:"varint,3,rep,packed,name=scores,proto3" json:"scores,omitempty"`
...
}
student.proto
syntax = "proto3"packagemessagerepeated=///* ... */

接下来,就可以在项目代码中直接使用了,以下是一个非常简单的例子,即证明被序列化的和反序列化后的实例,包含相同的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"log"

"github.com/golang/protobuf/proto"
)

func main() {
test := &Student{
Name: "geektutu",
Male: true,
Scores: []int32{98, 85, 88},
}
data, err := proto.Marshal(test)
if err != nil {
log.Fatal("marshaling error: ", err)
}
newTest := &Student{}
err = proto.Unmarshal(data, newTest)
if err != nil {
log.Fatal("unmarshaling error: ", err)
}
// Now test and newTest contain the same data.
if test.GetName() != newTest.GetName() {
log.Fatalf("data mismatch %q != %q", test.GetName(), newTest.GetName())
}
}
  • 保留字段(Reserved Field)

更新消息类型时,可能会将某些字段/标识符删除。这些被删掉的字段/标识符可能被重新使用,如果加载老版本的数据时,可能会造成数据冲突,在升级时,可以将这些字段/标识符保留(reserved),这样就不会被重新使用了,protoc 会检查。

1
2
3
4
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}

4 字段类型

4.1 标量类型(Scalar)

proto类型 go类型 备注 proto类型 go类型 备注
double float64 float float32
int32 int32 int64 int64
uint32 uint32 uint64 uint64
sint32 int32 适合负数 sint64 int64 适合负数
fixed32 uint32 固长编码,适合大于2^28的值 fixed64 uint64 固长编码,适合大于2^56的值
sfixed32 int32 固长编码 sfixed64 int64 固长编码
bool bool string string UTF8 编码,长度不超过 2^32
bytes []byte 任意字节序列,长度不超过 2^32

标量类型如果没有被赋值,则不会被序列化,解析时,会赋予默认值。

  • strings:空字符串
  • bytes:空序列
  • bools:false
  • 数值类型:0

4.2 枚举(Enumerations)

枚举类型适用于提供一组预定义的值,选择其中一个。例如我们将性别定义为枚举类型。

1
2
3
4
5
6
7
8
9
message Student {
string name = 1;
enum Gender {
FEMALE = 0;
MALE = 1;
}
Gender gender = 2;
repeated int32 scores = 3;
}
allow_alias
1
2
3
4
5
6
7
8
message EnumAllowAlias {
enum Status {
option allow_alias = true;
UNKOWN = 0;
STARTED = 1;
RUNNING = 1;
}
}

4.3 使用其他消息类型

Result
1
2
3
4
5
6
7
8
9
message SearchResponse {
repeated Result results = 1;
}

message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}

嵌套写也是支持的:

1
2
3
4
5
6
7
8
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}

如果定义在其他文件中,可以导入其他消息类型来使用:

1
import "myproject/other_protos.proto";

4.4 任意类型(Any)

Any 可以表示不在 .proto 中定义任意的内置类型。

1
2
3
4
5
6
import "google/protobuf/any.proto";

message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}

4.5 oneof

1
2
3
4
5
6
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}

4.6 map

1
2
3
message MapRequest {
map<string, int32> points = 1;
}

5 定义服务(Services)

SearchSearchRequestSearchResponse
1
2
3
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}

官方仓库也提供了一个插件列表,帮助开发基于 Protocol Buffer 的 RPC 服务。

6 protoc 其他参数

命令行使用方法

1
protoc --proto_path=IMPORT_PATH --<lang>_out=DST_DIR path/to/file.proto
--proto_path=IMPORT_PATH--_out=DST_DIR

7 推荐风格

my/package/my.packagemessage StudentRequest { ... }string status_code = 1enum FooBarservice FooService{ rpc GetSomething() }

附:参考