protobuf 詳解

2023-03-21 18:01:05

protobuf

protobuf概述

protobuf簡介

Protobuf是Protocol Buffers的簡稱,它是Google公司開發的一種資料描述語言,是一種輕便高效的結構化資料儲存格式,可以用於結構化資料序列化,或者說序列化 。它很適合做資料儲存RPC 資料交換格式。可用於通訊協定、資料儲存等領域的語言無關、平臺無關、可延伸的序列化結構資料格式。目前提供了 C++、Java、Python 三種語言的 API。

  1. protobuf是類似與json一樣的資料描述語言(資料格式)

  2. protobuf非常適合於RPC資料交換格式

注意:protobuf本身並不是和gRPC繫結的。它也可以被用於非RPC場景,如儲存等

protobuf的優劣勢

1)優勢:

  1. 序列化後體積相比Json和XML很小,適合網路傳輸

  2. 序列化反序列化速度很快,快於Json的處理速度

  3. 訊息格式升級和相容性還不錯

  4. 支援跨平臺多語言

2)劣勢:

  1. 應用不夠廣(相比xml和json)

  2. 二進位制格式導致可讀性差

  3. 缺乏自描述

protoc安裝(windows)

protoc就是protobuf的編譯器,它把proto檔案編譯成不同的語言

下載安裝protoc編譯器(protoc)

下載protobuf:https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protoc-3.20.1-win64.zip

解壓後,將目錄中的 bin 目錄的路徑新增到系統環境變數,然後開啟cmd輸入protoc檢視輸出資訊,此時則安裝成功

安裝protocbuf的go外掛(protoc-gen-go)

由於protobuf並沒直接支援go語言需要我們手動安裝相關外掛

protocol buffer編譯器需要一個外掛來根據提供的proto檔案生成 Go 程式碼,Go1.16+要使用下面的命令安裝外掛:

 go install google.golang.org/protobuf/cmd/protoc-gen-go@latest  // 目前最新版是v1.3.0

安裝grpc(grpc)

 go get -u -v google.golang.org/grpc@latest    // 目前最新版是v1.53.0

安裝grpc的go外掛(protoc-gen-go-grpc)

說明:在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

protobuf語法

 

 

protobuf語法

  • 型別:型別不僅可以是標量型別(intstring等),也可以是複合型別(enum等),也可以是其他message

  • 欄位名:欄位名比較推薦的是使用下劃線/分隔名稱

  • 欄位編號:一個message內每一個欄位編號都必須唯一的,在編碼後其實傳遞的是這個編號而不是欄位名

  • 欄位規則:訊息欄位可以是以下欄位之一

    • singular:格式正確的訊息可以有零個或一個欄位(但不能超過一個)。使用 proto3 語法時,如果未為給定欄位指定其他欄位規則,則這是預設欄位規則

    • optional:與 singular 相同,不過可以檢查該值是否明確設定

    • repeated:在格式正確的訊息中,此欄位型別可以重複零次或多次。系統會保留重複值的順序

    • map:這是一個成對的鍵值對欄位

  • 保留欄位:為了避免再次使用到已移除的欄位可以設定保留欄位。如果任何未來使用者嘗試使用這些欄位識別符號,編譯器就會報錯

簡單語法

proto檔案基本語法

 syntax = "proto3";              // 指定版本資訊,不指定會報錯
 package pb; // 後期生成go檔案的包名
 // message為關鍵字,作用為定義一種訊息型別
 message Person{
     string name = 1;   // 名字
     int32  age = 2 ;   // 年齡
 }
 
 enum test{
  int32 age = 0;
 }

protobuf訊息的定義(或者稱為描述)通常都寫在一個以 .proto 結尾的檔案中:

  1. 第一行指定正在使用proto3語法:如果不這樣做,協定緩衝區編譯器將假定正在使用proto2(這也必須是檔案的第一個非空的非註釋行)

  2. 第二行package指明當前是pb包(生成go檔案之後和Go的包名保持一致)

  3. message關鍵字定義一個Person訊息體,類似於go語言中的結構體,是包含一系列型別資料的集合。

    • 許多標準的簡單資料型別都可以作為欄位型別,包括boolint32floatdouble,和string

    • 也可以使用其他message型別作為欄位型別。

在message中有一個字串型別的value成員,該成員編碼時用1代替名字。在json中是通過成員的名字來繫結對應的資料,但是Protobuf編碼卻是通過成員的唯一編號來繫結對應的資料,因此Protobuf編碼後資料的體積會比較小,能夠快速傳輸,缺點是不利於閱讀。

message常見的資料型別與go中型別對比

.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 可以包含任意位元組序列

protobuff語法進階

message巢狀

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

repeated關鍵字

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欄位的預設值為空物件

enum關鍵字

在定義訊息型別時,可能會希望其中一個欄位有一個預定義的值列表

比如說,電話號碼欄位有個型別,這個型別可以是,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關鍵字

如果有一個包含許多欄位的訊息,並且最多隻能同時設定其中的一個欄位,則可以使用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;
    }
 }

定義RPC服務

如果需要將message與RPC一起使用,則可以在.proto檔案中定義RPC服務介面,protobuf編譯器將根據你選擇的語言生成RPC介面程式碼。範例如下:

 //定義RPC服務
 service HelloService {
     rpc Hello (Person)returns (Person);
 }

注意:預設protobuf編譯期間,不編譯服務,如果要想讓其編譯,需要使用gRPC

protobuf編譯

編譯器呼叫

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_out 引數

作用:指定go程式碼生成的基本路徑

  1. protocol buffer編譯器會將生成的Go程式碼輸出到命令列引數go_out指定的位置

  2. go_out標誌的引數是你希望編譯器編寫 Go 輸出的目錄

  3. 編譯器會為每個.proto 檔案輸入建立一個原始檔

  4. 輸出檔案的名稱是通過將.proto 擴充套件名替換為.pb.go 而建立的

--go_opt 引數

protoc-gen-go提供了--go_opt引數來為其指定引數,可以設定多個:

  1. paths=import:生成的檔案會按go_package路徑來生成,當然是在--go_out目錄

    • 例如,go_out/$go_package/pb_filename.pb.go

    • 如果未指定路徑標誌,這就是預設輸出模式

  2. paths=source_relative:輸出檔案與輸入檔案放在相同的目錄中

    • 例如,一個protos/buzz.proto輸入檔案會產生一個位於protos/buzz.pb.go的輸出檔案。

  3. module=$PREFIX:輸出檔案放在以 Go 包的匯入路徑命名的目錄中,但是從輸出檔名中刪除了指定的目錄字首。

    • 例如,輸入檔案 pros/buzz.proto,其匯入路徑為 example.com/project/protos/fizz 並指定example.com/projectmodule字首,結果會產生一個名為 pros/fizz/buzz.pb.go 的輸出檔案。

    • 在module路徑之外生成任何 Go 包都會導致錯誤,此模式對於將生成的檔案直接輸出到 Go 模組非常有用。

--proto_path 引數

--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` 目錄。如果需要,編譯器會自動建立巢狀的輸出子目錄,但不會建立輸出目錄本身

使用grpc的go外掛

安裝proto-gen-go-grpc

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

--go-grpc_out 引數

作用:指定grpc go程式碼生成的基本路徑

命令會產生的go檔案:

  1. protoc-gen-go:包含所有型別的序列化和反序列化的go程式碼

  2. protoc-gen-go-grpc:包含service中的用來給client呼叫的介面定義以及service中的用來給伺服器端實現的介面定義

--go-grpc_opt 引數

protoc-gen-go類似,protoc-gen-go-grpc提供 --go-grpc_opt 來指定引數,並可以設定多個

github.com/golang/protobufgoogle.golang.org/protobuf

github.com/golang/protobuf

  1. github.com/golang/protobuf 現在已經廢棄

  2. 它可以同時生成pb和gRPC相關程式碼的

用法:

 // 它在--go_out加了plugin關鍵字,paths引數有兩個選項,分別是 import 和 source_relative
 --go_out=plugins=grpc,paths=import:.  *.proto

google.golang.org/protobuf

  1. github.com/golang/protobuf的升級版本,v1.4.0之後github.com/golang/protobuf僅是google.golang.org/protobuf的包裝

  2. 它純粹用來生成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