Protocol Buffers and Go
在本教程中,我们将了解如何在 GO 语言中使用协议缓冲区。
What is Protocol Buffer
协议缓冲区是以结构化格式存储数据的数据格式。协议缓冲区格式的数据可以用多种语言进行序列化和反序列化。你可以把它想象成 JSON、XML,但它有很多优点。
我们先来看一个最简单的协议缓冲文件示例。
person.proto
syntax = "proto3";
option go_package = "./";
message Person {
string name = 1;
}
关于上述文件有几点需要注意。
- 这个文件只是我们的协议缓冲区结构的蓝图。目前还没有关联的数据。这与JSON/XML不同,在JSON/XML中,文件也表示实际数据。
- 在上面的文件中有一个人名消息,字段名类型为 string。“Proto3”意味着所编写的消息与 Protocol Buffer 版本 3 不兼容。
- 从上面的示例中,您可以注意到与 JSON 的一个不同之处,即它具有类型信息。这种类型的信息将有助于用不同语言自动生成代码。让我们看看 Golang 自动生成的例子
Auto Generation of GO Code
我们可以使用上面的 person.proto 文件生成相应的 Golang 代码。但要做到这一点,我们需要安装一些必须的东西:
- 首先安装协议缓冲区的 C++ 实现。每个平台都有自己的安装方式。
- Install Golang
- Install protoc-gen-go(proto 用于自动生成 golang 代码而提供的库)
go get -u http://github.com/golang/protobuf/protoc-gen-go
安装完成后,cd 到包含 person.proto 文件的目录。运行这个命令:
protoc -I ./ --go_out=./ ./person.proto
它将在同一目录中生成一个名为 person.pb.go 的数据访问 go 文件。
现在最大的问题是这个 person.pb.go 文件是什么,它是由 protoc 使用 person.proto 自动生成的。首先要注意的几点:
- Person 结构如下,看看如何利用 person.proto 文件的类型信息来知道 Name 是什么类型的字段Name是字符串
type Person struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
- 此外,proto包提供了对消息进行操作的函数,包括对二进制格式的转换和转换。
proto.Message接口定义了一个ProtoReflect
方法。这个方法返回一个protoreflect.Message
,它提供了一个基于反射的消息视图。
optimize_for
选项不影响Go代码生成器的输出。
func (*Person) ProtoMessage() {}
func (x *Person) GetName() string {
if x != nil {
return x.Name
}
return ""
}
因此,这个自动生成的文件基本上为 Person 结构生成数据访问器,并提供允许将 Person 结构类型序列化到字节数据或从实际字节数据序列化/反序列化的方法。
我们编写一个 main.go 程序来实际创建 Person 结构的具体对象。
main.go
package main
import (
"fmt"
"io/ioutil"
"log"
proto3 "test/proto3"
proto "github.com/golang/protobuf/proto"
)
func main() {
person := &proto3.Person{Name: "XXX"}
fmt.Printf("Person's name is %s\n", person.GetName())
//Now lets write this person object to file
out, err := proto.Marshal(person)
if err != nil {
log.Fatalf("Serialization error: %s", err.Error())
}
if err := ioutil.WriteFile("person.bin", out, 0644); err != nil {
log.Fatalf("Write File Error: %s ", err.Error())
}
fmt.Println("Write Success")
//Read from file
in, err := ioutil.ReadFile("person.bin")
if err != nil {
log.Fatalf("Read File Error: %s ", err.Error())
}
person2 := &proto3.Person{}
err2 := proto.Unmarshal(in, person2)
if err2 != nil {
log.Fatalf("DeSerialization error: %s", err.Error())
}
fmt.Println("Read Success")
fmt.Printf("Person2's name is %s\n", person2.GetName())
}
- 运行结果
- 目录结构
其中 go.mod 定义为 test。
请注意,在上面的程序中我们
- 我们将具体的 person 结构写入文件“person.bin”中。它是一个二进制文件,人类不可读。
- 我们也从文件中读取,它能够成功地读取和打印“ Person2’s name is XXX”
令人惊讶的是,“person.bin” 文件只有 5 个字节,相比之下,如果您使用相同的数据创建一个 JSON 文件,其大小将超过 15 个字节。此外,与 JSON 文件的序列化和反序列化相比,从字节到具体对象(反之亦然)的 Marshal 和 UnMarshal 也非常快。
Advantage
- 比相应的 JSON 和 XML 更清晰、更明确,因为它们还存储了类型信息。
- 存储的数据相对较小,几乎小 2 - 3 倍。
- 它要快得多。例如,使用协议缓冲区,序列化和反序列化速度更快
- 自动代码生成 – 您编写一个协议缓冲区文件,然后自动生成相应的 GO 文件
- GRPC 中使用了协议缓冲区,它是 REST 协议的下一代替代品
Conclusion
除了我们在文章中讨论的内容外,协议缓冲区还有很多其他功能。本文简要介绍了什么是协议缓冲区,以及与 JSON/XML 格式相比,协议缓冲区有哪些优势。