1. 程式人生 > >gRPC【RPC自定義http2.0協議傳輸】

gRPC【RPC自定義http2.0協議傳輸】

gRPC

 

簡介

  • gRPC是由Google公司開源的高效能RPC框架。

  • gRPC支援多語言

    gRPC原生使用C、Java、Go進行了三種實現,而C語言實現的版本進行封裝後又支援C++、C#、Node、ObjC、 Python、Ruby、PHP等開發語言

  • gRPC支援多平臺

    支援的平臺包括:Linux、Android、iOS、MacOS、Windows

  • gRPC的訊息協議使用Google自家開源的Protocol Buffers協議機制(proto3) 序列化

  • gRPC的傳輸使用HTTP/2標準,支援雙向流和連線多路複用

架構

C語言實現的gRPC支援多語言,其架構如下

 

使用方法

安裝

pip install grpc 
pip install grpcio-tools 

使用

  1. 使用Protocol Buffers(proto3)的IDL介面定義語言定義介面服務,編寫在文字檔案(以.proto為字尾名)中。
  2. 使用protobuf編譯器生成伺服器和客戶端使用的stub程式碼
  3. 編寫補充伺服器和客戶端邏輯程式碼

 

 

Protocol Buffers

 

Protocol Buffers 是一種與語言無關,平臺無關的可擴充套件機制,用於序列化結構化資料。使用Protocol Buffers 可以一次定義結構化的資料,然後可以使用特殊生成的原始碼輕鬆地在各種資料流中使用各種語言編寫和讀取結構化資料。

現在有許多框架等在使用Protocol Buffers。gRPC也是基於Protocol Buffers。 Protocol Buffers 目前有2和3兩個版本號。

在gRPC中推薦使用proto3版本。

1 文件結構

1) Protocol Buffers版本

Protocol Buffers文件的第一行非註釋行,為版本申明,不填寫的話預設為版本2。

syntax = "proto3"; 或者 syntax = "proto2"; 

2)Package包

Protocol Buffers 可以宣告package,來防止命名衝突。 Packages是可選的。

package foo.bar; message Open { ... } 

使用的時候,也要加上名稱空間,

message Foo {   ...   foo.bar.Open open = 1;   ... } 

注意:對於Python而言,package會被忽略處理,因為Python中的包是以檔案目錄來定義的。

3)匯入

Protocol Buffers 中可以匯入其它檔案訊息等,與Python的import類似。

import “myproject/other_protos.proto”; 

4)定義各種訊息和服務

訊息messge是用來定義資料的,服務service是用來gRPC的方法的。

2 註釋

Protocol Buffers 提供以下兩種註釋方式。

// 單行註釋 /*  多行註釋  多行註釋  */ 

3 資料型別

3.1 基本資料型別

.proto說明Python
double   float
float   float
int32 使用變長編碼,對負數編碼效率低, 如果你的變數可能是負數,可以使用sint32 int
int64 使用變長編碼,對負數編碼效率低,如果你的變數可能是負數,可以使用sint64 int/long
uint32 使用變長編碼 int/long
uint64 使用變長編碼 int/long
sint32 使用變長編碼,帶符號的int型別,對負數編碼比int32高效 int
sint64 使用變長編碼,帶符號的int型別,對負數編碼比int64高效 int/long
fixed32 4位元組編碼, 如果變數經常大於2^{28} 的話,會比uint32高效 int
fixed64 8位元組編碼, 如果變數經常大於2^{56} 的話,會比uint64高效 int/long
sfixed32 4位元組編碼 int
sfixed64 8位元組編碼 int/long
bool   bool
string 必須包含utf-8編碼或者7-bit ASCII text str
bytes 任意的位元組序列 str

3.2 列舉

在 Proto Buffers 中,我們可以定義列舉和列舉型別,

enum Corpus {     UNIVERSAL = 0;     WEB = 1;     IMAGES = 2;     LOCAL = 3;     NEWS = 4;     PRODUCTS = 5;     VIDEO = 6; } Corpus corpus = 4; 

列舉定義在一個訊息內部或訊息外部都是可以的,如果列舉是 定義在 message 內部,而其他 message 又想使用,那麼可以通過 MessageType.EnumType 的方式引用。

定義列舉的時候,我們要保證第一個列舉值必須是0,列舉值不能重複,除非使用 option allow_alias = true 選項來開啟別名。

enum EnumAllowingAlias {     option allow_alias = true;     UNKNOWN = 0;     STARTED = 1;     RUNNING = 1; } 

列舉值的範圍是32-bit integer,但因為列舉值使用變長編碼,所以不推薦使用負數作為列舉值,因為這會帶來效率問題。

4 訊息型別

Protocol Buffers使用message定義訊息資料。在Protocol Buffers中使用的資料都是通過message訊息資料封裝基本型別資料或其他訊息資料,對應Python中的類。

message SearchRequest {   string query = 1;   int32 page_number = 2;   int32 result_per_page = 3; } 

4.1 欄位編號

訊息定義中的每個欄位都有唯一的編號。這些欄位編號用於以訊息二進位制格式標識欄位,並且在使用訊息型別後不應更改。 請注意,1到15範圍內的欄位編號需要一個位元組進行編碼,包括欄位編號和欄位型別。16到2047範圍內的欄位編號佔用兩個位元組。因此,您應該為非常頻繁出現的訊息元素保留數字1到15。請記住為將來可能新增的常用元素留出一些空間。

最小的標識號可以從1開始,最大到2^29 - 1,或 536,870,911。不可以使用其中的[19000-19999]的標識號, Protobuf協議實現中對這些進行了預留。如果非要在.proto檔案中使用這些預留標識號,編譯時就會報警。同樣你也不能使用早期保留的標識號。

4.2 指定欄位規則

訊息欄位可以是以下之一:

  • singular:格式良好的訊息可以包含該欄位中的零個或一個(但不超過一個)。

  • repeated:此欄位可以在格式良好的訊息中重複任意次數(包括零)。將保留重複值的順序。對應Python的列表。

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

4.3 新增更多訊息型別

可以在單個.proto檔案中定義多個訊息型別。

message SearchRequest {   string query = 1;   int32 page_number = 2;   int32 result_per_page = 3; }  message SearchResponse {  ... } 

4.4 保留欄位

保留變數不被使用

如果通過完全刪除欄位或將其註釋來更新訊息型別,則未來使用者可以在對型別進行自己的更新時重用欄位編號。如果以後載入相同的舊版本,這可能會導致嚴重問題,包括資料損壞,隱私錯誤等。確保不會發生這種情況的一種方法是指定已刪除欄位的欄位編號(或名稱)reserved。如果將來的任何使用者嘗試使用這些欄位識別符號,protobuf編譯器將會報錯。

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

4.5 預設值

解析訊息時,如果編碼訊息不包含特定的單數元素,則解析物件中的相應欄位將設定為該欄位的預設值。這些預設值是特定於型別的:

  • 對於字串,預設值為空字串。
  • 對於位元組,預設值為空位元組。
  • 對於bools,預設值為false。
  • 對於數字型別,預設值為零。
  • 對於列舉,預設值是第一個定義的列舉值,該值必須為0。
  • 對於訊息欄位,未設定該欄位。它的確切值取決於語言。
  • 重複欄位的預設值為空(通常是相應語言的空列表)。

4.6 巢狀型別

你可以在其他訊息型別中定義、使用訊息型別,在下面的例子中,Result訊息就定義在SearchResponse訊息內,如:

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

如果要在其父訊息型別之外重用此訊息型別,使用

SearchResponse.Result 

5 map對映

如果要在資料定義中建立關聯對映,Protocol Buffers提供了一種方便的語法:

map< key_type, value_type> map_field = N ; 

其中key_type可以是任何整數或字串型別。請注意,列舉不是有效的key_type。value_type可以是除map對映型別外的任何型別。

例如,如果要建立專案對映,其中每條Project訊息都與字串鍵相關聯,則可以像下面這樣定義它:

map<string, Project> projects = 3 ; 
  • map的欄位可以是repeated。
  • 序列化後的順序和map迭代器的順序是不確定的,所以你不要期望以固定順序處理map
  • 當為.proto檔案產生生成文字格式的時候,map會按照key 的順序排序,數值化的key會按照數值排序。
  • 從序列化中解析或者融合時,如果有重複的key則後一個key不會被使用,當從文字格式中解析map時,如果存在重複的key,則解析可能會失敗。
  • 如果為對映欄位提供鍵但沒有值,則欄位序列化時的行為取決於語言。在Python中,使用型別的預設值。

6 oneof

如果你的訊息中有很多可選欄位, 並且同時至多一個欄位會被設定, 你可以加強這個行為,使用oneof特性節省記憶體。

為了在.proto定義oneof欄位, 你需要在名字前面加上oneof關鍵字, 比如下面例子的test_oneof:

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

然後你可以增加oneof欄位到 oneof 定義中. 你可以增加任意型別的欄位, 但是不能使用repeated 關鍵字。

7 定義服務

Protocol Buffers使用service定義RPC服務。

message HelloRequest {   string greeting = 1; }  message HelloResponse {   string reply = 1; }  service HelloService {   rpc SayHello (HelloRequest) returns (HelloResponse) {} }
 
syntax = "proto3";

message UserRequest {
string user_id=1;
int32 channel_id=2;
int32 article_num=3;
int64 time_stamp=4;
}

message Track {
string click=1;
string collect=2;
string share=3;
string read=4;
}

message Article {
int64 article_id=1;
Track track=2;
}

message ArticleResponse {
string exposure=1;
int64 time_stamp=2;
repeated Article recommends=3;
}

service UserRecommend {
rpc user_recommend(UserRequest) returns(ArticleResponse) {}