Protobuf是Protocol Buffers的簡稱,它是Google公司開發的一種資料描述語言,是一種輕便高效的結構化資料儲存格式,可以用於結構化資料序列化,或者說序列化 。它很適合做資料儲存或 RPC 資料交換格式。可用於通訊協定、資料儲存等領域的語言無關、平臺無關、可延伸的序列化結構資料格式。目前提供了 C++、Java、Python 三種語言的 API。
protobuf是類似與json一樣的資料描述語言(資料格式)
protobuf非常適合於RPC資料交換格式
注意:protobuf
本身並不是和gRPC
繫結的。它也可以被用於非RPC場景,如儲存等
1)優勢:
序列化後體積相比Json和XML很小,適合網路傳輸
序列化反序列化速度很快,快於Json的處理速度
訊息格式升級和相容性還不錯
支援跨平臺多語言
2)劣勢:
應用不夠廣(相比xml和json)
二進位制格式導致可讀性差
缺乏自描述
protoc就是protobuf的編譯器,它把proto檔案編譯成不同的語言
下載protobuf:https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protoc-3.20.1-win64.zip
解壓後,將目錄中的 bin 目錄的路徑新增到系統環境變數,然後開啟cmd輸入protoc
檢視輸出資訊,此時則安裝成功
由於protobuf並沒直接支援go語言需要我們手動安裝相關外掛
protocol buffer編譯器需要一個外掛來根據提供的proto檔案生成 Go 程式碼,Go1.16+要使用下面的命令安裝外掛:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest // 目前最新版是v1.3.0
go get -u -v google.golang.org/grpc@latest // 目前最新版是v1.53.0
說明:在google.golang.org/protobuf
中,protoc-gen-go
純粹用來生成pb序列化相關的檔案,不再承載gRPC程式碼生成功能,所以如果要生成grpc相關的程式碼需要安裝grpc-go相關的外掛:protoc-gen-go-grpc
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest // 目前最新版是v1.3.0
型別:型別不僅可以是標量型別(int
、string
等),也可以是複合型別(enum
等),也可以是其他message
欄位名:欄位名比較推薦的是使用下劃線/分隔名稱
欄位編號:一個message內每一個欄位編號都必須唯一的,在編碼後其實傳遞的是這個編號而不是欄位名
欄位規則:訊息欄位可以是以下欄位之一
singular
:格式正確的訊息可以有零個或一個欄位(但不能超過一個)。使用 proto3 語法時,如果未為給定欄位指定其他欄位規則,則這是預設欄位規則
optional
:與 singular
相同,不過可以檢查該值是否明確設定
repeated
:在格式正確的訊息中,此欄位型別可以重複零次或多次。系統會保留重複值的順序
map
:這是一個成對的鍵值對欄位
保留欄位:為了避免再次使用到已移除的欄位可以設定保留欄位。如果任何未來使用者嘗試使用這些欄位識別符號,編譯器就會報錯
syntax = "proto3"; // 指定版本資訊,不指定會報錯
package pb; // 後期生成go檔案的包名
// message為關鍵字,作用為定義一種訊息型別
message Person{
string name = 1; // 名字
int32 age = 2 ; // 年齡
}
enum test{
int32 age = 0;
}
protobuf訊息的定義(或者稱為描述)通常都寫在一個以 .proto 結尾的檔案中:
第一行指定正在使用proto3
語法:如果不這樣做,協定緩衝區編譯器將假定正在使用proto2(這也必須是檔案的第一個非空的非註釋行)
第二行package指明當前是pb包(生成go檔案之後和Go的包名保持一致)
message關鍵字定義一個Person訊息體,類似於go語言中的結構體,是包含一系列型別資料的集合。
許多標準的簡單資料型別都可以作為欄位型別,包括bool
,int32
, float
,double
,和string
也可以使用其他message型別作為欄位型別。
在message中有一個字串型別的value成員,該成員編碼時用1代替名字。在json中是通過成員的名字來繫結對應的資料,但是Protobuf編碼卻是通過成員的唯一編號來繫結對應的資料,因此Protobuf編碼後資料的體積會比較小,能夠快速傳輸,缺點是不利於閱讀。
.proto型別 | Go型別 | 介紹 |
---|---|---|
double | float64 | 64位元浮點數 |
float | float32 | 32位元浮點數 |
int32 | int32 | 使用可變長度編碼。編碼負數效率低下——如果你的欄位可能有負值,請改用sint32。 |
int64 | int64 | 使用可變長度編碼。編碼負數效率低下——如果你的欄位可能有負值,請改用sint64。 |
uint32 | uint32 | 使用可變長度編碼。 |
uint64 | uint64 | 使用可變長度編碼。 |
sint32 | int32 | 使用可變長度編碼。符號整型值。這些比常規int32s編碼負數更有效。 |
sint64 | int64 | 使用可變長度編碼。符號整型值。這些比常規int64s編碼負數更有效。 |
fixed32 | uint32 | 總是四位元組。如果值通常大於228,則比uint 32更有效 |
fixed64 | uint64 | 總是八位元組。如果值通常大於256,則比uint64更有效 |
sfixed32 | int32 | 總是四位元組。 |
sfixed64 | int64 | 總是八位元組。 |
bool | bool | 布林型別 |
string | string | 字串必須始終包含UTF - 8編碼或7位ASCII文字 |
bytes | []byte | 可以包含任意位元組序列 |
messsage除了能放簡單資料型別外,還能存放另外的message型別:
syntax = "proto3"; // 指定版本資訊,不指定會報錯
package pb; // 後期生成go檔案的包名
// message為關鍵字,作用為定義一種訊息型別
message Person{
string name = 1; // 名字
int32 age = 2 ; // 年齡
// 定義一個message
message PhoneNumber {
string number = 1;
int64 type = 2;
}
PhoneNumber phone = 3;
}
message成員編號,可以不從1開始,但是不能重複,不能使用19000 - 19999
repeadted關鍵字類似與go中的切片,編譯之後對應的也是go的切片,用法如下:
syntax = "proto3"; // 指定版本資訊,不指定會報錯
package pb; // 後期生成go檔案的包名
// message為關鍵字,作用為定義一種訊息型別
message Person{
string name = 1; // 名字
int32 age = 2 ; // 年齡
// 定義一個message
message PhoneNumber {
string number = 1;
int64 type = 2;
}
repeated PhoneNumber phone = 3;
}
解析資料時,如果編碼的訊息不包含特定的單數元素,則解析物件物件中的相應欄位將設定為該欄位的預設值
不同型別的預設值不同,具體如下:
對於字串,預設值為空字串
對於位元組,預設值為空位元組
對於bools,預設值為false
對於數位型別,預設值為零
對於列舉,預設值是第一個定義的列舉值,該值必須為0。
repeated欄位預設值是空列表
message欄位的預設值為空物件
在定義訊息型別時,可能會希望其中一個欄位有一個預定義的值列表
比如說,電話號碼欄位有個型別,這個型別可以是,home,work,mobile
我們可以通過enum在訊息定義中新增每個可能值的常數來非常簡單的執行此操作。範例如下:
syntax = "proto3"; // 指定版本資訊,不指定會報錯
package pb; // 後期生成go檔案的包名
// message為關鍵字,作用為定義一種訊息型別
message Person{
string name = 1; // 名字
int32 age = 2 ; // 年齡
// 定義一個message
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phone = 3;
}
// enum為關鍵字,作用為定義一種列舉型別
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
如上,enum的第一個常數對映為0,每個列舉定義必須包含一個對映到零的常數作為其第一個元素。這是因為:
必須有一個零值,以便我們可以使用0作為數位預設值。
零值必須是第一個元素,以便與proto2語意相容,其中第一個列舉值始終是預設值。
enum還可以為不同的列舉常數指定相同的值來定義別名。如果想要使用這個功能必須將allow_alias
選項設定為true,負責編譯器將報錯。範例如下:
syntax = "proto3"; // 指定版本資訊,不指定會報錯
package pb; // 後期生成go檔案的包名
// message為關鍵字,作用為定義一種訊息型別
message Person{
string name = 1; // 名字
int32 age = 2 ; // 年齡
// 定義一個message
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phone = 3;
}
// enum為關鍵字,作用為定義一種列舉型別
enum PhoneType {
// 如果不設定將報錯
option allow_alias = true;
MOBILE = 0;
HOME = 1;
WORK = 2;
Personal = 2;
}
如果有一個包含許多欄位的訊息,並且最多隻能同時設定其中的一個欄位,則可以使用oneof功能,範例如下:
message Person{
string name = 1; // 名字
int32 age = 2 ; // 年齡
//定義一個message
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phone = 3;
oneof data{
string school = 5;
int32 score = 6;
}
}
如果需要將message與RPC一起使用,則可以在.proto
檔案中定義RPC服務介面,protobuf編譯器將根據你選擇的語言生成RPC介面程式碼。範例如下:
//定義RPC服務
service HelloService {
rpc Hello (Person)returns (Person);
}
注意:預設protobuf編譯期間,不編譯服務,如果要想讓其編譯,需要使用gRPC
protobuf 編譯是通過編譯器 protoc 進行的,通過這個編譯器,我們可以把 .proto 檔案生成 go,Java,Python,C++, Ruby或者C# 程式碼
可以使用以下命令來通過 .proto 檔案生成go程式碼(以及grpc程式碼)
// 將當前目錄中的所有 .proto檔案進行編譯生成go程式碼
protoc --go_out=./ --go_opt=paths=source_relative *.proto
protobuf 編譯器會把 .proto 檔案編譯成 .pd.go 檔案
作用:指定go程式碼生成的基本路徑
protocol buffer編譯器會將生成的Go程式碼輸出到命令列引數go_out
指定的位置
go_out
標誌的引數是你希望編譯器編寫 Go 輸出的目錄
編譯器會為每個.proto
檔案輸入建立一個原始檔
輸出檔案的名稱是通過將.proto
擴充套件名替換為.pb.go
而建立的
protoc-gen-go
提供了--go_opt
引數來為其指定引數,可以設定多個:
paths=import
:生成的檔案會按go_package
路徑來生成,當然是在--go_out
目錄
例如,go_out/$go_package/pb_filename.pb.go
如果未指定路徑標誌,這就是預設輸出模式
paths=source_relative
:輸出檔案與輸入檔案放在相同的目錄中
例如,一個protos/buzz.proto
輸入檔案會產生一個位於protos/buzz.pb.go
的輸出檔案。
module=$PREFIX
:輸出檔案放在以 Go 包的匯入路徑命名的目錄中,但是從輸出檔名中刪除了指定的目錄字首。
例如,輸入檔案 pros/buzz.proto
,其匯入路徑為 example.com/project/protos/fizz
並指定example.com/project
為module
字首,結果會產生一個名為 pros/fizz/buzz.pb.go
的輸出檔案。
在module路徑之外生成任何 Go 包都會導致錯誤,此模式對於將生成的檔案直接輸出到 Go 模組非常有用。
--proto_path=IMPORT_PATH
IMPORT_PATH是 .proto 檔案所在的路徑,如果忽略則預設當前目錄。
如果有多個目錄則可以多次呼叫--proto_path,它們將會順序的被存取並執行匯入。
使用範例:
protoc --proto_path=src --go_out=out --go_opt=paths=source_relative foo.proto bar/baz.proto
// 編譯器將從 `src` 目錄中讀取輸入檔案 `foo.proto` 和 `bar/baz.proto`,並將輸出檔案 `foo.pb.go` 和 `bar/baz.pb.go` 寫入 `out` 目錄。如果需要,編譯器會自動建立巢狀的輸出子目錄,但不會建立輸出目錄本身
在google.golang.org/protobuf
中,protoc-gen-go
純粹用來生成pb序列化相關的檔案,不再承載gRPC程式碼生成功能。生成gRPC相關程式碼需要安裝grpc-go相關的外掛protoc-gen-go-grpc
// 安裝protoc-gen-go-grpc
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest // 目前最新版是v1.3.0
生成grpc的go程式碼:
// 主要是--go_grpc_out引數會生成go程式碼
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative *.proto
作用:指定grpc go程式碼生成的基本路徑
命令會產生的go檔案:
protoc-gen-go
:包含所有型別的序列化和反序列化的go程式碼
protoc-gen-go-grpc
:包含service中的用來給client呼叫的介面定義以及service中的用來給伺服器端實現的介面定義
和protoc-gen-go
類似,protoc-gen-go-grpc
提供 --go-grpc_opt
來指定引數,並可以設定多個
github.com/golang/protobuf
和 google.golang.org/protobuf
github.com/golang/protobuf
github.com/golang/protobuf
現在已經廢棄
它可以同時生成pb和gRPC相關程式碼的
用法:
// 它在--go_out加了plugin關鍵字,paths引數有兩個選項,分別是 import 和 source_relative
--go_out=plugins=grpc,paths=import:. *.proto
它github.com/golang/protobuf
的升級版本,v1.4.0
之後github.com/golang/protobuf
僅是google.golang.org/protobuf
的包裝
它純粹用來生成pb序列化相關的檔案,不再承載gRPC程式碼生成功能,生成gRPC相關程式碼需要安裝grpc-go相關的外掛protoc-gen-go-grpc
用法:
// 它額外新增了引數--go-grpc_out以呼叫protoc-gen-go-grpc外掛生成grpc程式碼
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative *.proto