Protobuf入門

星見遙發表於2021-02-18

在Kafka中,傳送的訊息是位元組陣列,因此就需要一個方法來將訊息物件序列化為位元組陣列,在消費者端再反序列化為物件。最常用的序列化格式就是JSON了。雖然JSON對人類非常友好,但是對於機器來說,更容易進行序列化和反序列化的格式還是二進位制的格式。

Protobuf(Protocol buffers)是由Google開發的一種二進位制協議,用於對結構化資料進行序列化和反序列化。這種格式佔用空間更少,更加簡單易於維護,同時擁有更好的效能。對於機器之間的通訊,Protobuf是比XML和JSON等格式更好的一種選擇。

Protobuf的使用相比之下更加複雜,需要編寫.proto格式的檔案來定義資料格式,之後通過protoc編譯器將其編譯到對應的語言,之後再在程式中引用。

使用

安裝

首先就是安裝protoc編譯器,這個編譯器可以直接到github上下載二進位制包,解壓到對應位置並設定PATH即可。

之後就是安裝對應語言的客戶端,對於golang,執行下面兩條語句安裝就可以了:

go get github.com/golang/protobuf/proto
go get github.com/golang/protobuf/protoc-gen-go

編譯

之後新建一個.proto檔案,在其中定義訊息的格式:

syntax = "proto3";
package test;

option go_package = "proto/test";

message Award {
    int64 uid = 1;
    int64 awardId = 2;
    string userName = 3;
}

然後使用protoc編譯器將其編譯到go檔案就行了:

protoc --go_out=. proto/*.proto

在go程式中引入生成的包就可以進行序列化和反序列化了:

import pb "proto/test"

func marshal() {
    award := &pb.Award{
		Uid:      628,
		AwardId:  1,
		UserName: "Haruka",
	}
	msg, err := proto.Marshal(award)
}

func unmarshal() {
    award := &pb.Award{}
	if err := proto.Unmarshal(msg.Value, award); err != nil {
		panic(err)
	}
}

proto3語言

.proto檔案的第一行就是syntax = "proto3";,用於宣告該檔案是proto3版本的。之後可以宣告package用於避免命名衝突,最後就可以定義message了。

欄位ID

message的每個欄位都要分配一個唯一的ID,最小是1最大是2^29 - 1,同時不能使用 1900019999的ID。ID可以任意分配,但是115只會佔用1個位元組,而16~2047會佔用2個位元組,因此應儘量從小開始分配,並將小的分配給最經常出現的欄位。

可以使用reserved來對欄位名和ID進行保留,一般用於為未來新增欄位保留,或者保留刪除欄位來避免之後的欄位使用,防止發生衝突:

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

欄位型別

欄位的型別預設是singular,也就是隻能出現一次或0次,如果在欄位宣告前加上repeated的話就可以出現多次。

protobuf的標量型別有以下幾種:double float int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool string bytes

protobuf中還可以使用列舉和複合型別。例如可以通過import "google/protobuf/timestamp.proto"來使用timestamp型別。

protobuf資料型別的預設值規則如下,如果一個欄位被設定為預設值,則其不會被序列化:

  • string bytes型別預設為空
  • bool型別預設為false
  • 數值型別預設為0
  • 列舉型別預設為第一個,即0
  • 複合型別取決於語言

列舉型別

message SearchRequest {
  enum Corpus {
    option allow_alias = true;
    UNIVERSAL = 0;
    WEB = 1;
    TEST = 1;
    IMAGES = 2;
  }
  Corpus corpus = 4;
}

通過enum來宣告一個列舉,注意列舉中必須要有0值用來作為預設值,同時0值應該是第一個元素,以與proto2相容。通過option allow_alias = true;來允許兩個列舉元素有相同的值,即這兩個元素可以相互替代。

複合型別

在一個message中,可以嵌入其他的message作為複合型別:

message SearchResponse {
  repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}

protobuf中還有其他高階型別,如Any oneof map等,就不詳細介紹了。

相關文章