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
使用
- 使用Protocol Buffers(proto3)的IDL接口定義語言定義接口服務,編寫在文本文件(以
.proto
為后綴名)中。 - 使用protobuf編譯器生成服務器和客戶端使用的stub代碼
- 編寫補充服務器和客戶端邏輯代碼
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方法