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

  • 2019 年 10 月 3 日
  • 筆記

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) {}  }  

  


程式碼生成

編譯生成程式碼

python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. reco.proto

  • -I表示搜索proto文件中被導入文件的目錄
  • --python_out表示保存生成Python文件的目錄,生成的文件中包含介面定義中的數據類型
  • --grpc_python_out表示保存生成Python文件的目錄,生成的文件中包含介面定義中的服務類型

在toutiao-backend/common/rpc目錄下執行上述命令,會自動生成如下兩個rpc調用輔助程式碼模組:

  • reco_pb2.py 保存根據介面定義文件中的數據類型生成的python類
  • reco_pb2_grpc.py 保存根據介面定義文件中的服務方法類型生成的python調用RPC方法