原來可以這麼使用 Protobuf

1. Protobuf 簡介

1.1 Protobuf 是什麼

Protocol Buffers (簡稱 Protobuf) 是 Google 公司開源的一種輕便高效的結構化數據存儲格式, 以及用於序列化和反序列化結構化數據的代碼生成器。它可以用於通訊協議和數據存儲等領域。

Protobuf 是以 .proto 文件形式定義結構化數據的方式和格式。

並且通過代碼生成器生成各平臺 (Java、C++、Python、Go 等) 的數據訪問類, 這些生成的類可以用來在對應的語言中解析、序列化 Protobuf 數據。

1.2 Protobuf 的優點

Protobuf 作爲一種數據格式和工具, 有以下優點:

(1) 性能高, 序列化和反序列化速度很快

Protobuf 採用二進制格式存儲數據, 相比 XML 和 JSON 格式, 可以大幅減少數據體積, serialization 和 deserialization 的性能也更優。這對於高性能場景非常有利。

(2) 跨平臺, 多語言支持廣泛

Protobuf 提供了標準的 .proto 文件格式和數據描述語法, 然後可以通過 protoc 工具, 自動生成各主流語言的數據訪問類, 如 Java、C++、Python、Go 等。

這保證了在不同平臺和不同語言 scenarios 下, 可以解析和驗證一致的 Protobuf 數據。

(3) 定義結構化數據格式, 方便維護升級

通過 .proto 文件定義數據格式, 可以清晰界定不同版本數據格式的兼容關係, 格式修改後也方便使用舊格式數據。

(4) 數據體積小, 便於存儲和傳輸

相比 XML、JSON,Protobuf 的二進制編碼可以大幅減小數據體積, 節省存儲和網絡傳輸成本。

(5) 擴展性好, 靈活支持新增字段

通過定義可選和 Required 字段, 可以輕鬆添加和刪除消息中的字段, 而不影響已有字段的序號, 便於數據格式的擴展和演進。

2. Go 語言中使用 Protobuf

2.1 在 Go 語言中安裝 Protobuf 庫

在 Go 語言中使用 Protobuf 主要依賴 google 開源的 golang/protobuf 庫, 使用以下命令安裝:

go get -u github.com/golang/protobuf/proto

安裝完成後, 可以在代碼中 import 此庫:

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

2.2 使用 protoc 編譯. proto 文件

編寫 .proto 文件後, 需要使用 protoc 命令生成 Go 代碼, 例如:

protoc --go_out=. message.proto

這會根據 message.proto 中的消息定義, 生成 Go 語言版本的訪問類, 存放在 message.pb.go 文件中。

2.3 Protobuf 消息的編碼和解碼

golang/protobuf 庫中主要包含下面兩個函數, 用來序列化和反序列化 Protobuf 消息:

func Marshal(pb Message) ([]byte, error)
func Unmarshal(buf []byte, pb Message) error

其中 Message 是一個滿足 protobuf.Message 接口的 Protobuf 消息對象, 可以是通過 .proto 生成的 pb.go 文件中定義的類型, 也可以是動態消息。

這兩個函數可以方便的在任意 Go 類型與 Protobuf 二進制格式之間進行轉換。

2.4 Protobuf 服務的定義

除了用於數據存儲、網絡通信外, Protobuf 也可以用來定義服務接口 (RPC 服務)。語法如下:

service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}
message SearchRequest {
  string query = 1; 
  int32 page_number = 2;  
  int32 result_per_page = 3;
}
message SearchResponse {
  repeated Result results = 1;
}
message Result {
  string url = 1;  
  string title = 2;  
  repeated string snippets = 3;
}

這樣就定義了一個 RPC 服務 interface, 包含一個 Search 方法。然後客戶端和服務器端通過實現這個 interface, 來發送、處理服務請求和響應。

服務端需要實現服務接口定義的方法, 客戶端需要調用這個接口方法, 傳遞請求參數, 獲取響應結果。

3. Protobuf 消息的定義

通過 .proto 文件, 可定義 Protobuf 中的消息結構。Protobuf 消息由一系列字段組成, 使用 message 定義, 每個消息可包含多種類型的字段。

3.1 消息類型

Protobuf 支持標量類型、複合類型的消息定義。

  • 標量類型: 包括整型、浮點型、布爾型、字符串等;

  • 複合類型: 主要是其他消息類型, 一個消息字段可以引用其他消息類型。

3.2 標量類型

語法格式如下:

[修飾符] 類型名 字段名 = 字段號;

常用標量類型和修飾符總結如下:

  • int32,int64 - 有符號整型

  • uint32,uint64 - 無符號整型

  • bool - 布爾類型

  • string - 字符串類型

  • bytes - 字節數組

  • float,double - 浮點類型

  • repeated - 重複類型, 表示數組

  • required - 必填字段

  • optional - 可選字段, 默認值

示例:

message Person {
  required string name = 1;
  required int32 age = 2;  
  optional string email = 3;
}

這定義了一個 Person 消息, 包含必填的 name、age 字段和可選的 email 字段。

3.3 定義 request 和 response

可定義一對請求和響應消息, 用於 RPC 服務接口的輸入和輸出參數。

語法結構如下:

// SearchRequest請求消息
message SearchRequest {
  required string query = 1;
  optional int32 page = 2;  
  ...
}
// SearchResponse響應消息  
message SearchResponse {
  repeated Result results = 1;
  optional int32 total_results = 2;
}

這樣通過一對請求響應消息消息, 定義了服務接口的入參和返回值格式。

3.4 import 公共 proto 文件

爲了重用消息定義和其他 .proto 文件的內容, 可以用 import 語句導入其他 .proto 文件。

例如:

import "other/other.proto";

這樣就可以直接引用 other.proto 中定義的消息、枚舉等。

3.5 使用 options 設置項

Protobuf 支持自定義 options 字段, 對消息、枚舉進行註解或設置生成參數:

message Foo {
  optional string text = 1 [(custom_option) = "hello world"]; 
}

這爲 text 字段添加一個自定義 option 註解。

4. Go 語言 Protobuf 實踐

下面以一個完整的例子, 演示下 Go 語言中使用 Protobuf 的整個流程。

4.1 定義 Protobuf 消息

編寫一個 person.proto 文件, 定義 Person 消息格式:

syntax = "proto3";
package tutorial;
message Person {
  string name = 1;
  int32 age = 2; 
  string email = 3;
}

4.2 生成 Go 代碼

然後使用 protoc 命令根據 person.proto 生成 Go 語言代碼:

protoc --go_out=. person.proto

這會生成一個 person.pb.go 文件。

4.3 發送、接收 Protobuf 消息

有了生成的 Go 訪問類, 就可很方便的在 Go 代碼中處理 Person 消息。

例如序列化和反序列化:

package main
import (
    "log"
    "github.com/golang/protobuf/proto"
    "path/to/personpb" // Import the generated personpb package
)
func main() {
    p := &personpb.Person{
        Name:  "John Doe",
        Age:   30,
        Email: "john@email.com",
    }
    data, err := proto.Marshal(p)
    if err != nil {
        log.Fatal("marshaling error: ", err)
    }
    // Handle the marshaled data, for example, print it
    log.Printf("Marshaled data: %v", data)
    // If you want to do something with the marshaled data, you can use it here
}

4.4 Protobuf 服務端和客戶端

可利用 Protobuf 來定義服務接口, 下面演示服務器和客戶端的實現。

person.proto 中定義服務:

service PersonService {
  rpc GetPerson(GetPersonRequest) returns (Person) {}
}
message GetPersonRequest {
  string name = 1;
}

這定義了一個 PersonService, 包含獲取 Person 的 GetPerson 方法。

在服務器代碼中實現這個接口:

type server struct{}
func (s *server) GetPerson(ctx context.Context, req *GetPersonRequest) (*Person, error) {
  // 從數據庫中獲取Person對象並返回
}
func main() {
  lis, err := net.Listen("tcp", ":50051")
  srv := grpc.NewServer()
  pb.RegisterPersonServiceServer(srv, &server{}) 
  srv.Serve(lis)
}

在客戶端, 可調用這個接口:

conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
client := pb.NewPersonServiceClient(conn)
resp, err := client.GetPerson(context.Background(), &req)

這樣通過 gRPC 框架, 就可以訪問服務器定義的 Protobuf 服務。

4.5 與 gRPC 集成

Protobuf 定義的消息和服務可以很容易的在 gRPC 框架中使用, gRPC 正是通過 Protobuf 接口定義實現服務通信的。

服務器端實現 Protobuf 接口, 客戶端調用接口, 二者通過 gRPC 通訊。

5. Protobuf 使用注意事項和經驗

5.1 版本控制

爲了兼容舊版本, 在修改消息定義時, 應該謹慎地創建新字段而不是刪除舊字段。

5.2 向後兼容

  • 對於 int32、uint32、int64、uint64、bool、string、bytes 字段, 新代碼可以讀寫舊消息, 前向後兼容性是沒有問題的。

  • 對於 repeated 字段, 刪除或者順序改變字段號, 會造成不兼容。

  • 新增 optional 或 repeated 字段, 前向後兼容性是沒有問題。但是新增 required 字段則會造成解析問題。

所以新增字段時, 應使用 optional 而不是 required。

5.3 包含大數據量字段

由於 Protobuf 是二進制編碼的, 如果有字段包含非常大的數據 (如圖片、視頻), 會大幅增加消息大小。

這時可以考慮通過指針引用獨立文件的形式, 避免消息體積過大。

6. 總結

6.1 Protobuf 優缺點

相比 XML 和 JSON 數據格式, Protobuf 作爲一種高效的結構化數據存儲和交換格式, 具有以下優點:

  • 編碼效率高, 序列化後數據體積小

  • 解析速度快

  • 支持數據格式升級與兼容

  • 支持定義服務接口

  • 跨平臺跨語言, 通過編譯支持各平臺訪問

當然也存在一些限制, 比如不適合處理頻繁修改的數據格式, 不支持數據查詢等。所以 Protobuf 在很多性能敏感、跨平臺的場景下, 可以發揮很好的作用。

6.2 在 Go 語言項目中的作用

在 Go 語言中, Protobuf 可以用於:

  • 定義項目中的數據結構體

  • 網絡服務的請求響應參數和結果

  • RPC 服務接口定義

  • 數據存儲格式定義

通過 Protobuf 接口定義, 可以實現服務端和客戶端的松耦合。

並且利用 Protobuf 接口語言無關性, 可以支持多語言訪問後端 Go 服務, 實現更好的語言融合。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/meML_v9-Nwsa-_oQ1BMAgg