1. 程式人生 > 其它 >Protocal 分散式 傳輸資料

Protocal 分散式 傳輸資料

1 Protocol Buffers 簡介
protobuf 即 Protocol Buffers,是一種輕便高效的結構化資料儲存格式,與語言、平臺無關,可擴充套件可序列化。protobuf 效能和效率大幅度優於 JSON、XML 等其他的結構化資料格式。protobuf 是以二進位制方式儲存的,佔用空間小,但也帶來了可讀性差的缺點。protobuf 在通訊協議和資料儲存等領域應用廣泛。例如著名的分散式快取工具 Memcached 的 Go 語言版本groupcache 就使用了 protobuf 作為其 RPC 資料格式。

Protobuf 在 .proto 定義需要處理的結構化資料,可以通過 protoc 工具,將 .proto 檔案轉換為 C、C++、Golang、Java、Python 等多種語言的程式碼,相容性好,易於使用。

2 安裝

# 下載安裝包
$ wget https://github.com/protocolbuffers/protobuf/releases/download/v3.11.2/protoc-3.11.2-linux-x86_64.zip
# 解壓到 /usr/local 目錄下
$ sudo 7z x protoc-3.11.2-linux-x86_64.zip -o/usr/local

如果不想安裝在 /usr/local 目錄下,可以解壓到其他的其他,並把解壓路徑下的 bin 目錄 加入到環境變數即可。

如果能正常顯示版本,則表示安裝成功。

$ protoc --version
libprotoc 3.11.2

2.2 protoc-gen-go
我們需要在 Golang 中使用 protobuf,還需要安裝 protoc-gen-go,這個工具用來將 .proto 檔案轉換為 Golang 程式碼。

1
go get -u github.com/golang/protobuf/protoc-gen-go
protoc-gen-go 將自動安裝到 $GOPATH/bin 目錄下,也需要將這個目錄加入到環境變數中。

3 定義訊息型別
接下來,我們建立一個非常簡單的示例,student.proto

syntax = "proto3";
package main;

// this is a comment
message Student {
  string name = 1;
  bool male = 2;
  repeated int32 scores = 3;
}

在當前目錄下執行:

$ protoc --go_out=. *.proto
$ ls
student.pb.go  student.proto

即是,將該目錄下的所有的 .proto 檔案轉換為 Go 程式碼,我們可以看到該目錄下多出了一個 Go 檔案 student.pb.go。這個檔案內部定義了一個結構體 Student,以及相關的方法:

type Student struct {
	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
	Male bool `protobuf:"varint,2,opt,name=male,proto3" json:"male,omitempty"`
	Scores []int32 `protobuf:"varint,3,rep,packed,name=scores,proto3" json:"scores,omitempty"`
	...
}

逐行解讀student.proto

protobuf 有2個版本,預設版本是 proto2,如果需要 proto3,則需要在非空非註釋第一行使用 syntax = "proto3" 標明版本。
package,即包名宣告符是可選的,用來防止不同的訊息型別有命名衝突。
訊息型別 使用 message 關鍵字定義,Student 是型別名,name, male, scores 是該型別的 3 個欄位,型別分別為 string, bool 和 []int32。欄位可以是標量型別,也可以是合成型別。
每個欄位的修飾符預設是 singular,一般省略不寫,repeated 表示欄位可重複,即用來表示 Go 語言中的陣列型別。
每個字元 =後面的數字稱為識別符號,每個欄位都需要提供一個唯一的識別符號。識別符號用來在訊息的二進位制格式中識別各個欄位,一旦使用就不能夠再改變,識別符號的取值範圍為 [1, 2^29 - 1] 。
.proto 檔案可以寫註釋,單行註釋 //,多行註釋 /* ... */
一個 .proto 檔案中可以寫多個訊息型別,即對應多個結構體(struct)。
接下來,就可以在專案程式碼中直接使用了,以下是一個非常簡單的例子,即證明被序列化的和反序列化後的例項,包含相同的資料。

package main

import (
	"log"

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

func main() {
	test := &Student{
		Name: "geektutu",
		Male:  true,
		Scores: []int32{98, 85, 88},
	}
	data, err := proto.Marshal(test)
	if err != nil {
		log.Fatal("marshaling error: ", err)
	}
	newTest := &Student{}
	err = proto.Unmarshal(data, newTest)
	if err != nil {
		log.Fatal("unmarshaling error: ", err)
	}
	// Now test and newTest contain the same data.
	if test.GetName() != newTest.GetName() {
		log.Fatalf("data mismatch %q != %q", test.GetName(), newTest.GetName())
	}
}

保留欄位(Reserved Field)
更新訊息型別時,可能會將某些欄位/識別符號刪除。這些被刪掉的欄位/識別符號可能被重新使用,如果載入老版本的資料時,可能會造成資料衝突,在升級時,可以將這些欄位/識別符號保留(reserved),這樣就不會被重新使用了,protoc 會檢查。

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

4 欄位型別
4.1 標量型別(Scalar)
proto型別 go型別 備註 proto型別 go型別 備註
double float64 float float32
int32 int32 int64 int64
uint32 uint32 uint64 uint64
sint32 int32 適合負數 sint64 int64 適合負數
fixed32 uint32 固長編碼,適合大於2^28的值 fixed64 uint64 固長編碼,適合大於2^56的值
sfixed32 int32 固長編碼 sfixed64 int64 固長編碼
bool bool string string UTF8 編碼,長度不超過 2^32
bytes []byte 任意位元組序列,長度不超過 2^32
標量型別如果沒有被賦值,則不會被序列化,解析時,會賦予預設值。

strings:空字串
bytes:空序列
bools:false
數值型別:0
4.2 列舉(Enumerations)
列舉型別適用於提供一組預定義的值,選擇其中一個。例如我們將性別定義為列舉型別。

message Student {
  string name = 1;
  enum Gender {
    FEMALE = 0;
    MALE = 1;
  }
  Gender gender = 2;
  repeated int32 scores = 3;
}

列舉型別的第一個選項的識別符號必須是0,這也是列舉型別的預設值。
別名(Alias),允許為不同的列舉值賦予相同的識別符號,稱之為別名,需要開啟allow_alias選項。

message EnumAllowAlias {
  enum Status {
    option allow_alias = true;
    UNKOWN = 0;
    STARTED = 1;
    RUNNING = 1;
  }
}

4.3 使用其他訊息型別
Result是另一個訊息型別,在 SearchReponse 作為一個訊息欄位型別使用。

message SearchResponse {
  repeated Result results = 1; 
}

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

巢狀寫也是支援的:

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

如果定義在其他檔案中,可以匯入其他訊息型別來使用:

import "myproject/other_protos.proto";
4.4 任意型別(Any)
Any 可以表示不在 .proto 中定義任意的內建型別。

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}

4.5 oneof

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}

4.6 map

message MapRequest {
  map<string, int32> points = 1;
}

5 定義服務(Services)
如果訊息型別是用來遠端通訊的(Remote Procedure Call, RPC),可以在 .proto 檔案中定義 RPC 服務介面。例如我們定義了一個名為 SearchService 的 RPC 服務,提供了 Search 介面,入參是 SearchRequest 型別,返回型別是 SearchResponse

service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
官方倉庫也提供了一個外掛列表,幫助開發基於 Protocol Buffer 的 RPC 服務。

6 protoc 其他引數
命令列使用方法

protoc --proto_path=IMPORT_PATH --_out=DST_DIR path/to/file.proto
--proto_path=IMPORT_PATH:可以在 .proto 檔案中 import 其他的 .proto 檔案,proto_path 即用來指定其他 .proto 檔案的查詢目錄。如果沒有引入其他的 .proto 檔案,該引數可以省略。
--_out=DST_DIR:指定生成程式碼的目標資料夾,例如 –go_out=. 即生成 GO 程式碼在當前資料夾,另外支援 cpp/java/python/ruby/objc/csharp/php 等語言
7 推薦風格
檔案(Files)

檔名使用小寫下劃線的命名風格,例如 lower_snake_case.proto
每行不超過 80 字元
使用 2 個空格縮排
包(Packages)

包名應該和目錄結構對應,例如檔案在my/package/目錄下,包名應為 my.package
訊息和欄位(Messages & Fields)

訊息名使用首字母大寫駝峰風格(CamelCase),例如message StudentRequest { ... }
欄位名使用小寫下劃線的風格,例如 string status_code = 1
列舉型別,列舉名使用首字母大寫駝峰風格,例如 enum FooBar,列舉值使用全大寫下劃線隔開的風格(CAPITALS_WITH_UNDERSCORES ),例如 FOO_DEFAULT=1
服務(Services)

RPC 服務名和方法名,均使用首字母大寫駝峰風格,例如service FooService{ rpc GetSomething() }

本部落格只是本人在找別人的部落格中學習的筆記,如有冒犯,請聯絡我刪除