gRPC 一開始由 google 開發,是一款語言中立、平臺中立、開源的遠程過程調用(RPC)系統。 本文經過一個簡單的 Hello World 例子來向您介紹 gRPC 。
gRPC 也是基於如下理念:定義一個服務,指定其可以被遠程調用的方法(包含參數和返回類型)。在服務端實現這個接口,並運行一個 gRPC 服務器來處理客戶端調用。在客戶端擁有一個存根可以像服務端同樣的方法。html
在 gRPC 裏客戶端應用能夠像調用本地對象同樣直接調用另外一臺不一樣的機器上服務端應用的方法,使得咱們可以更容易地建立分佈式應用和服務。java
gRPC 客戶端和服務端能夠在多種環境中運行和交互,而且能夠用任何 gRPC 支持的語言來編寫。python
gRPC 支持 C++ Java Python Go Ruby C# Node.js PHP Dart 等語言
gRPC 默認使用 protocol buffers,這是 Google 開源的一種輕便高效的結構化數據存儲格式,能夠用於結構化數據串行化,或者說序列化。它很適合作數據存儲或 RPC 數據交換格式。linux
參考文檔:gRPC Python Quickstartgit
python -m pip install grpcio # 或者 sudo python -m pip install grpcio # 在 El Capitan OSX 系統下可能會看到如下報錯 $ OSError: [Errno 1] Operation not permitted: '/tmp/pip-qwTLbI-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/six-1.4.1-py2.7.egg-info' # 可使用如下命令 python -m pip install grpcio --ignore-installed
Python gPRC tools 包含 protocol buffer 編譯器和用於從 .proto
文件生成服務端和客戶端代碼的插件github
python -m pip install grpcio-tools
在 github 頁面protobuf Buffers能夠下載二進制源碼,下載後執行如下命令安裝:小程序
tar -zxvf protobuf-all-3.5.1.tar cd protobuf-all-3.5.1 ./configure make make install >> protoc --version libprotoc 3.5.1 # 安裝成功
由於是要使用 Protobuf + Python 測試,因此還要安裝 python運行環境。protobuf Buffers python 文檔設計模式
# 打開 python 目錄 cd python python setup.py install # 安裝 python 運行環境
先來看一個很是簡單的例子。假設你想定義一個「搜索請求」的消息格式,每個請求含有一個查詢字符串、你感興趣的查詢結果所在的頁數,以及每一頁多少條查詢結果。能夠採用以下的方式來定義消息類型的.proto文件了:api
syntax = "proto3"; // 聲明使用 proto3 語法 message SearchRequest { string query = 1; // 每一個字段都要指定數據類型 int32 page_number = 2; // 這裏的數字2 是標識符,最小的標識號能夠從1開始,最大到2^29 - 1, or 536,870,911。不可使用其中的[19000-19999] int32 result_per_page = 3; // 這裏是註釋,使用 // }
這個指定語法必須是文件的非空非註釋的第一行
。SearchRequest
消息格式有三個字段,在消息中承載的數據分別對應於每個字段。其中每一個字段都有一個名字和一種類型。雙斜槓(//)
語法格式。[1,15]以內的標識號在編碼的時候會佔用一個字節。[16,2047]以內的標識號則佔用2個字節。因此應該爲那些頻繁出現的消息元素保留 [1,15]以內的標識號。切記:要爲未來有可能添加的、頻繁出現的標識號預留一些標識號。
所指定的消息字段修飾符必須是以下之一:bash
repeated:在一個格式良好的消息中,這種字段能夠重複任意屢次(包括0次)。重複的值的順序會被保留。
在proto3中,repeated的標量域默認狀況蝦使用packed。
message Test4 { repeated int32 d = 4 [packed=true]; }
一個標量消息字段能夠含有一個以下的類型——該表格展現了定義於.proto文件中的類型,以及與之對應的、在自動生成的訪問類中定義的類型:
.proto Type | Notes | C++ Type | Java Type | Python Type[2] | Go Type | Ruby Type |
---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | |
float | float | float | float | float32 | Float | |
int32 | 使用變長編碼,對於負值的效率很低,若是你的域有可能有負值,請使用sint64替代 | int32 | int | int | int32 | Fixnum 或者 Bignum(根據須要) |
uint32 | 使用變長編碼 | uint32 | int | int/long | uint32 | Fixnum 或者 Bignum(根據須要) |
uint64 | 使用變長編碼 | uint64 | long | int/long | uint64 | Bignum |
sint32 | 使用變長編碼,這些編碼在負值時比int32高效的多 | int32 | int | int | int32 | Fixnum 或者 Bignum(根據須要) |
sint64 | 使用變長編碼,有符號的整型值。編碼時比一般的int64高效。 | int64 | long | int/long | int64 | Bignum |
fixed32 | 老是4個字節,若是數值老是比老是比228大的話,這個類型會比uint32高效。 | uint32 | int | int | uint32 | Fixnum 或者 Bignum(根據須要) |
fixed64 | 老是8個字節,若是數值老是比老是比256大的話,這個類型會比uint64高效。 | uint64 | long | int/long | uint64 | Bignum |
sfixed32 | 老是4個字節 | int32 | int | int | int32 | Fixnum 或者 Bignum(根據須要) |
sfixed64 | 老是8個字節 | int64 | long | int/long | int64 | Bignum |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | |
string | 一個字符串必須是UTF-8編碼或者7-bit ASCII編碼的文本。 | string | String | str/unicode | string | String (UTF-8) |
bytes | 可能包含任意順序的字節數據。 | string | ByteString | str | []byte | String (ASCII-8BIT) |
當一個消息被解析的時候,若是被編碼的信息不包含一個特定的singular元素,被解析的對象鎖對應的域被設置位一個默認值,對於不一樣類型指定以下:
對於可重複域的默認值是空(一般狀況下是對應語言中空列表)。
你能夠在其餘消息類型中定義、使用消息類型,在下面的例子中,Result消息就定義在SearchResponse消息內,如:
message SearchResponse { message Result { string url = 1; string title = 2; repeated string snippets = 3; } repeated Result results = 1; }
在 message SearchResponse 中,定義了嵌套消息 Result
,並用來定義SearchResponse
消息中的results
域。
當用protocol buffer編譯器來運行.proto文件時,編譯器將生成所選擇語言的代碼,這些代碼能夠操做在.proto文件中定義的消息類型,包括獲取、設置字段值,將消息序列化到一個輸出流中,以及從一個輸入流中解析消息。
這裏咱們用Python 編譯一下,看獲得什麼:
// 文件名 hello.proto syntax = "proto3"; package hello; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloReply { string message = 1; }
使用如下命令編譯:
python -m grpc_tools.protoc -I./ --python_out=. --grpc_python_out=. ./hello.proto
生成了兩個文件:
hello_pb2.py
此文件包含生成的 request(HelloRequest
) 和 response(HelloReply
) 類。hello_pb2_grpc.py
此文件包含生成的 客戶端(GreeterStub
)和服務端(GreeterServicer
)的類。源碼地址爲https://github.com/grpc/grpc/blob/master/examples/protos/helloworld.proto
雖然如今已經生成了服務端和客戶端代碼,可是咱們還須要手動實現以及調用的方法。
建立和運行 Greeter
服務能夠分爲兩個部分:
在當前目錄,打開文件 greeter_server.py,實現一個新的函數:
from concurrent import futures import time import grpc import hello_pb2 import hello_pb2_grpc _ONE_DAY_IN_SECONDS = 60 * 60 * 24 class Greeter(hello_pb2_grpc.GreeterServicer): # 工做函數 def SayHello(self, request, context): return hello_pb2.HelloReply(message='Hello, %s!' % request.name) def serve(): # gRPC 服務器 server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) hello_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server) server.add_insecure_port('[::]:50051') server.start() # start() 不會阻塞,若是運行時你的代碼沒有其它的事情可作,你可能須要循環等待。 try: while True: time.sleep(_ONE_DAY_IN_SECONDS) except KeyboardInterrupt: server.stop(0) if __name__ == '__main__': serve()
在當前目錄,打開文件 greeter_client.py,實現一個新的函數:
from __future__ import print_function import grpc import hello_pb2 import hello_pb2_grpc def run(): channel = grpc.insecure_channel('localhost:50051') stub = hello_pb2_grpc.GreeterStub(channel) response = stub.SayHello(hello_pb2.HelloRequest(name='goodspeed')) print("Greeter client received: " + response.message) if __name__ == '__main__': run()
對於返回單個應答的 RPC 方法("response-unary" 方法),gRPC Python 同時支持同步(阻塞)和異步(非阻塞)的控制流語義。對於應答流式 RPC 方法,調用會當即返回一個應答值的迭代器。調用迭代器的
next()
方法會阻塞,直到從迭代器產生的應答變得可用。
python greeter_server.py
python greeter_client.py # output Greeter client received: Hello, goodspeed!
源碼地址: https://github.com/grpc/grpc/tree/master/examples/python
最後,感謝女友支持和包容,比❤️
也能夠在公號輸入如下關鍵字獲取歷史文章:公號&小程序
| 設計模式
| 併發&協程