【GoCN酷Go推薦】protobuf生成Go代碼插件gogo/protobuf
從 JSON 開始
談到序列化,大家最先想到的可能是 JSON 或者 XML,這兩種序列化協(xié)議都是基于文本的編碼方式進行數(shù)據(jù)傳輸。類似的還有 YAML 等。
JSON 擁有許多優(yōu)點,使之成為最廣泛使用的序列化協(xié)議之一。如 JSON 協(xié)議簡單,人眼可讀,序列化后十分簡潔且解析速度快。此外,JSON 具備 JavaScript 的先天性支持,被廣泛應(yīng)用于 Web Browser 的應(yīng)用場景中,并且是 Ajax 的事實標準協(xié)議。
JSON 的適用場景比較多,典型應(yīng)用場景包括:
公司外部之間傳輸數(shù)據(jù)量相對較小,實時性要求相對低的服務(wù) 基于 Web browser 的 Ajax 請求 接口經(jīng)常發(fā)生變化,并對可調(diào)式性要求較高的場景,例如移動 App 與服務(wù)端的通信
然而,由于 JSON 本身的設(shè)計的一些特點,在一些場景下使用 JSON 仍然不是最優(yōu)解。如:
需要標準的 IDL ,增強參與各方業(yè)務(wù)約束的場景。由于 JSON 協(xié)議往往只能使用文檔的方式來進行約定,這可能會給調(diào)試帶來一些不便與不明確
對性能和簡潔性有較高要求的場景。JSON 在一些語言中的序列化和反序列化需要采用反射機制,所以在性能要求特別高場景下可能不是最優(yōu)解
對于大數(shù)據(jù)量服務(wù)或持久化場景。JSON 進行序列化的額外空間開銷比較大,這也意味著較大的內(nèi)存和磁盤開銷
對于以上場景, 使用一些基于 IDL ,存儲方案為二進制存儲的序列化方案則更為合適, 如 ProtoBuf、Thrift、avro等。
IDL: 參與通訊的各方需要對通訊的內(nèi)容需要做相關(guān)的約定。為了建立一個與語言和平臺無關(guān)的約定,這個約定需要采用與具體開發(fā)語言、平臺無關(guān)的語言來進行描述。這種語言被稱為接口描述語言(IDL),采用IDL撰寫的協(xié)議約定稱之為IDL文件。
什么是 Protobuf
ProtoBuf 是 Protocol Buffers 的簡稱 ,是 Google 公司開源的一種語言無關(guān)、平臺無關(guān)、可擴展的序列化結(jié)構(gòu)數(shù)據(jù)的方案,它可用于(數(shù)據(jù))通信協(xié)議、數(shù)據(jù)存儲等。
ProtoBuf 是上述場景中比較適用的序列化方案之一。ProtoBuf 非常靈活,高效,我們可以通過定義 IDL (在這里是proto)文件,然后使用生成的源代碼輕松的在各種數(shù)據(jù)流中使用各種語言進行編寫和讀取結(jié)構(gòu)數(shù)據(jù)。甚至可以更新數(shù)據(jù)結(jié)構(gòu),而不破壞由舊數(shù)據(jù)結(jié)構(gòu)編譯的已部署程序。
上文提到,同類型的序列化方案還有 Thrift 和 Avro。其中 Thrift 并不僅僅是序列化協(xié)議,他被嵌入到 Thrift 框架中,這導(dǎo)致其很難和其他傳輸層協(xié)議共同使用。Avro 由于沒有成熟的 JS 實現(xiàn),不適合 Web 環(huán)境, 也 導(dǎo)致其使用場景也比較有限。
目前 gRPC 默認的序列化方式是 ProtoBuf。
ProtoBuf 包含序列化格式的定義、各種語言的庫以及一個 IDL 編譯器。正常情況下需要我們定義 proto 文件,然后使用IDL 編譯器編譯成需要的語言。
一個簡單的 proto 例子
syntax = "proto3"; // proto 版本,建議使用 proto3
option go_package = "main/proto"; // 包名聲明符
message SearchRequestParam { // message 類型
enum Type { // 枚舉類型
PC = 0;
Mobile = 1;
}
string query_text = 1; // 字符串類型 | 后面的「1」為數(shù)字標識符,在消息定義中需要唯一
int32 limit = 3; // 整型
Type type = 4; // 枚舉類型
}
message SearchResultPage {
repeated string result = 1; // 「repeated」表示字段可以重復(fù)任意多次(包括0次)
int32 num_results = 2;
}
// test.proto
代碼中的只是一些比較普通的字段定義,還有一些復(fù)雜的一些字段定義,如Oneof、Map、Reserved等可以參考官方文檔。
生成 Go 代碼
在 .proto 文件中定義好需要處理的結(jié)構(gòu)化數(shù)據(jù)后,可以通過 protoc 工具,將 .proto 文件轉(zhuǎn)換為 C、C++、Golang、Java、Python 等多種語言的代碼。我們這里嘗試一下生成 Golang 語言代碼。
首先需要安裝 protoc 工具
# 下載安裝包 (Mac)
$ wget https://github.com/protocolbuffers/protobuf/releases/download/v3.15.6/protoc-3.15.6-osx-x86_64.zip
# 解壓到 /usr/local 目錄下
$ unzip protoc-3.15.6-osx-x86_64.zip -d protoc-3.15.6-osx-x86_64
$ mv protoc-3.5.0-osx-x86_64/bin/protoc /usr/local/bin/protoc
# 執(zhí)行如下表示成功:
$ protoc --version
libprotoc 3.15.6
然后安裝一個官方的生成 Golang 代碼的插件 protoc-gen-go
$ go get -u github.com/golang/protobuf/protoc-gen-go
現(xiàn)在在 proto文件所在目錄下執(zhí)行以下命令以生成go文件
$ protoc --go_out=. test.proto
protoc 命令還可以使用-I參數(shù)指定搜索 import 的 proto 的文件夾。其他參數(shù)詳情可以參考官方文檔。
我們可以在目錄下看到一個 test.pb.go 文件。其中主要結(jié)構(gòu)體如下:
type SearchRequestParam struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
QueryText string `protobuf:"bytes,1,opt,name=query_text..."`
Limit int32 `protobuf:"varint,3,opt,name=limit,proto3"...."`
Type SearchRequestParam_Type `protobuf:"varint,4,opt,name=type,proto3..."`
}
type SearchResultPage struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Result []string `protobuf:"bytes,1,rep,name=result,proto3...."`
NumResults int32 `protobuf:"varint,2,opt,name=num_results,json=numResults,proto3..."`
接下來,就可以在項目代碼中直接使用了。
gogo/protobuf 是什么
在上文中,我們安裝了一個「生成 Golang 代碼的插件 protoc-gen-go」,這個插件其實是 golang 官方提供的 一個Protobuf api 實現(xiàn)。而我們的主角gogo/protobuf是基于 golang/protobuf 的一個增強版實現(xiàn)。
gogo 庫基于官方庫開發(fā),增加了很多的功能,包括:
快速的序列化和反序列化 更規(guī)范的Go數(shù)據(jù)結(jié)構(gòu) goprotobuf 兼容 可選擇的產(chǎn)生一些輔助方法,減少使用中的代碼輸入 可以選擇產(chǎn)生測試代碼和 benchmark 代碼 其它序列化格式
目前很多知名的項目都在使用該庫,如 etcd、k8s、tidb、docker swarmkit 等。
gogo/protobuf 如何使用
https://github.com/gogo/protobuf 根目錄下我們可以看到有很多文件夾,其中「protoc-gen」為前綴的為生成代碼的插件,其他「proto」、「protobuf」、「gogoproto」等為庫文件。
gogo 庫目前有三種生成代碼的方式
gofast: 速度優(yōu)先,但此方式不支持其它 gogoprotobuf 的擴展選項。
$ go get github.com/gogo/protobuf/protoc-gen-gofast
$ protoc --gofast_out=. myproto.proto
gogofast、gogofaster、gogoslick: 更快的速度、會生成更多的代碼。$ go get github.com/gogo/protobuf/proto
$ go get github.com/gogo/protobuf/{binary} //protoc-gen-gogofast、protoc-gen-gogofaster 、protoc-gen-gogoslick
$ go get github.com/gogo/protobuf/gogoproto
$ protoc -I=. -I=$GOPATH/src -I=$GOPATH/src/github.com/gogo/protobuf/protobuf --{binary}_out=. myproto.proto // 這里的{binary}不包含「protoc-gen」前綴gogofast類似gofast,但是會引入 gogoprotobuf 庫。gogofaster類似gogofast,但是不會產(chǎn)生XXX_unrecognized類的指針字段,可以減少垃圾回收時間。gogoslick類似gogofaster,但是會增加一些額外的string、gostring和equal method等。protoc-gen-gogo: 最快的速度,最多的可定制化$ go get github.com/gogo/protobuf/proto
$ go get github.com/gogo/protobuf/jsonpb
$ go get github.com/gogo/protobuf/protoc-gen-gogo
$ go get github.com/gogo/protobuf/gogoproto可以通過擴展選項高度定制序列化。
gogo/protobuf 提供了非常多的擴展選項,以便在產(chǎn)生代碼的時候進行更多的控制。上文提到的擴展選項這里有一個全面的介紹:extensions,擴展選項里主要包含一些生成快速序列化反序列化代碼的可選項、生成更規(guī)范的Golang 數(shù)據(jù)結(jié)構(gòu)的可選項、goprotobuf 兼容的可選項,一些產(chǎn)生輔助方法的可選項、產(chǎn)生測試代碼和benchmark 的可選項,還可以增加 jsontag 等。
有同學(xué)對以上多個生成方式的序列化性能做了一些壓測,在一般需求下,性能差距并不是很大,protoc-gen-gofast方式基本可以滿足大多數(shù)場景。
最后,生成的 go 語言代碼在項目中使用就非常簡單了,一般只需要使用proto.Marshal,proto.Unmarshal 方法就可以了,下面是一個例子:
package main
import (
"fmt"
"log"
zaproto "git.xxxxx.com/data/za-proto/proto"
"github.com/gogo/protobuf/proto"
)
func main() {
req := &zaproto.SearchRequestParam{
QueryText: "xxxxxx",
Limit: 10,
Type: zaproto.SearchRequestParam_PC,
}
data, err := proto.Marshal(req)
if err != nil {
log.Fatal("Marshal err : err")
}
// send data
fmt.Println(string(data))
var respData []byte
var result = zaproto.SearchResultPage{}
if err = proto.Unmarshal(respData, &result); err == nil {
fmt.Println(result)
} else {
log.Fatal("Unmarshal err : err")
}
}
參考
alecthomas/go_serialization_benchmarks: Benchmarks of Go serialization methods (github.com)
So you want to use GoGo Protobuf (jbrandhorst.com)
Schema evolution in Avro, Protocol Buffers and Thrift — Martin Kleppmann’s blog
Language Guide | Protocol Buffers | Google Developers
序列化和反序列化 - 美團技術(shù)團隊 (meituan.com)
Protobuf 有沒有比 JSON 快 5 倍?-InfoQ
幾種Go序列化庫的性能比較 | 鳥窩 (colobu.com)
思考gRPC :為什么是protobuf | 橫云斷嶺的專欄 (hengyunabc.github.io)
幾種Go序列化庫的性能比較 | 鳥窩 (colobu.com)
還想了解更多嗎?
更多請查看:https://github.com/tidwall/gjson
歡迎加入我們GOLANG中國社區(qū):https://gocn.vip/
《酷Go推薦》招募:
各位Gopher同學(xué),最近我們社區(qū)打算推出一個類似GoCN每日新聞的新欄目《酷Go推薦》,主要是每周推薦一個庫或者好的項目,然后寫一點這個庫使用方法或者優(yōu)點之類的,這樣可以真正的幫助到大家能夠?qū)W習(xí)到新的庫,并且知道怎么用。
大概規(guī)則和每日新聞類似,如果報名人多的話每個人一個月輪到一次,歡迎大家報名!
點擊 閱讀原文 即刻報名
