translate https://grpc.io/docs/tutorials/basic/python.html
這個 tutorial 提供 gRPC 基本的介紹給 python programmer 們
走過這個Example 你會學到:
- 如何定義一個service 在 .proto 檔內
- 如何透過 protocol buffer compiler 來產 Server 跟 Clinet 的 code
- 使用 python gRPC API 為你的服務 寫一個 簡單的 clinet 與 service 範例
這邊假設你已經大概看過 protocol buffers 的 Overview 也大概熟悉惹. 如果需要m, 你可以找到 到 proto3 language guide, Python generated code guide 更多的 資訊.
Why use gRPC?
這個example 是一個簡單的 route mapping application 能夠讓 clients 取得 他們自己route 的 特徵資訊, 並且 建立一個 summary of the route, 還有 exchange route information(像是 traffic server 跟其他clients 更新的紀錄)
在gRPC的協助下 你的 service 只要定義一次.proto file 就能在任何支援gRPC的語言實做 clients 跟 servers, 反過來說 複雜跨語言 的 各種環境 就已經被你透過 gRPC 處理了. 你也能取得各種 protocol buffers的優點: efficient serialization, simple IDL(接口定義語言), easy interface updating.
Example code and setup
這份 tutorial 的 code 可以在這邊找到grpc/grpc/examples/python/route_guide.
$ git clone -b v1.19.0 https://github.com/grpc/grpc$ cd grpc/examples/python/route_guide
你也需要先安裝這篇 一些相關的tools
Defining the service
你的第一步(正如你從Overview 看到的) 是去使用protocal buffers定義 gRPC service 得 request method 還有request types,(examples/protos/route_guide.proto 這邊看一下完整範例)
定義一個 service, 你必須要 在 .proto file裡 named service
:
service RouteGuide {
// (Method definitions not shown)
}
接著你就可以定義rpc 的 methods 在 你的 service 的定義裡, 給定他們的request 跟 response types. gRPC 讓你定義4種 service method, 這些都可以被使用在 RouteGuide 的service 內.
- simple RPC : client 會用stub 送request 過來 並且等待response 回來, 就像是一般的 function call
rpc GetFeature(Point) returns (Feature) {}
- response-streaming RPC: 當 Clients 送 request 到 server 後 拿到 一個 stream 用來讀 一個sequence 的 messages back. Client 可以讀stream 直到沒有任何message 回來. 就像 example 中展示的, 你須 在 response method 上 放 stream 這個 關鍵字
rpc ListFeatures(Rectangle) returns (stream Feature) {}
- request-streaming RPC: 當 client 將一個序列的 messages 當作stream 送到 server, 當clinet 完成 writing messages, 他會等待server 將其全部讀完, 且回復訊息, 你須在 request-streaming method 中的 response type 上擺關鍵字 stream
rpc ListFeatures(Rectangle) returns (stream Feature) {}
- bidirectionally-streaming RPC: 當兩邊都互送 sequence of messages 使用一個 read-write stream, 兩個 stream 是獨立的作業著, 所以 clients 跟 servers 都可以不用在乎讀寫的順序, 舉例來說 server 並不會等到收到所有clinet 的訊息, 他可以一邊讀 一邊寫, 或是其他讀寫的組合, message 在stream 中的排序有備保留, 你可以指定 stream 於 request 及 response.
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
同時 你的 .proto 也必須定義這些message type, 舉例來說
// the range +/- 180 degrees (inclusive).
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
Generating client and server code
接著你需要 從 proto service definition 做出 gRPC 的 client 與 server 的interfaces
首先 先安裝 grpcio-tools
$ pip install grpcio-tools
使用以下指令來產生 Python Code:
$ python -m grpc_tools.protoc -I../../protos --python_out=. --grpc_python_out=. ../../protos/route_guide.proto
注意, 該repo已經提供了一個 generated code 在 example directory, 你應該重新使用grpc_tools
產生files 而不是使用既存的檔案. generated files: route_guide_pb2.py
and route_guide_pb2_grpc.py
內含:
- route_guide.proto 中定義的 classes for the messages
- route_guide.proto 中定義的 classes for the service :
RouteGuideStub
, 讓 clients 可以用來呼叫 RouteGuide RPCs
RouteGuideServicer
定義了 RouteGuide service 實做 interface
- 一個 用來 定義service 的 function
add_RouteGuideServicer_to_server
, 把 RouteGuideServicer 加到grpc.Server
注意, 2
在 pb2 代表, 這份code 是遵循 Protocol Buffers Python API version 2. Version 1 已經過時了. 且這個數字與 proto file中的 syntax = "proto3"
or syntax = "proto2"
無關
Creating the server
首先讓我們看一下如何建立 RouteGuide
server, 如果你只對 如何產生 gRPC clients 感興趣, 你可以跳過這個章節到後面的 Creating the client
建立並創造一個 RouteGuide
server 分成兩個 work items:
- 從 service definition 來 實做 server interface, function 應該要能真的動起來
- 跑一個 gRPC server 來聽 request, 且 回傳 response
example of RouteGuide
server: examples/python/route_guide/route_guide_server.py.
Implementing RouteGuide
route_guide_server.py
有一個 RouteGuideServicer
class 其繼承 了剛剛grpc_tools.protoc
產生的 route_guide_pb2_grpc.RouteGuideServicer
class
# 這個 class 要把 RouteGuideServicer 宣告但是沒實做的function 做完
class RouteGuideServicer(route_guide_pb2_grpc.RouteGuideServicer):
Simple RPC
先來看一下最簡單的type, GetFeature 就是從 Client 拿到一個 Point 跟回傳 相對應的 DB 中 的 feature information
def GetFeature(self, request, context):
feature = get_feature(self.db, request)
if feature is None:
return route_guide_pb2.Feature(name="", location=request)
else:
return feature
這個 request 變數是 route_guide_pb2.Point
型別, 而context grpc.ServicerContext
提供了 RPC-特定的資訊 像是 timeout limit, 最後其回傳 route_guide_pb2.Feature
Object
Response-streaming RPC
現在讓我們看下一個 method ListFeatures
這是一個 response-streaming RPC, 他會送多個 Feature 到 Client端
def ListFeatures(self, request, context):
left = min(request.lo.longitude, request.hi.longitude)
right = max(request.lo.longitude, request.hi.longitude)
top = max(request.lo.latitude, request.hi.latitude)
bottom = min(request.lo.latitude, request.hi.latitude)
for feature in self.db:
if (feature.location.longitude >= left and
feature.location.longitude <= right and
feature.location.latitude >= bottom and
feature.location.latitude <= top):
yield feature
這裡 request message 是 route_guide_pb2.Rectangle
的object, 情境是 Client 想要這些 object 的Feature
. 這個method 會 yield 零個或多個 response 太神喇.
Request-streaming RPC
再來是 requrest-streaming method RecordRoute
, Client 來 iterator of request, 然後要回一個 single response value
def RecordRoute(self, request_iterator, context):
point_count = 0
feature_count = 0
distance = 0.0
prev_point = None
start_time = time.time()
for point in request_iterator:
point_count += 1
if get_feature(self.db, point):
feature_count += 1
if prev_point:
distance += get_distance(prev_point, point)
prev_point = point
elapsed_time = time.time() - start_time
return route_guide_pb2.RouteSummary(point_count=point_count,
feature_count=feature_count,
distance=int(distance),
elapsed_time=int(elapsed_time))
Bidirectional streaming RPC
最後我們看一下 bidirectionally-streaming method RouteChat
.
def RouteChat(self, request_iterator, context):
prev_notes = []
for new_note in request_iterator:
for prev_note in prev_notes:
if prev_note.location == new_note.location:
yield prev_note
prev_notes.append(new_note)
這個方法 結合 request-streaming method and the response-streaming method, 傳進來一個 iterator 然後他自身也會回傳一個 iterator
Starting the server
一旦你 implement 所有的 RouteGuide
methods 之後, 下一步就是要跑起來你的gRPC server 這樣 clients 就能直接用你的 service:
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
route_guide_pb2_grpc.add_RouteGuideServicer_to_server(
RouteGuideServicer(), server)
server.add_insecure_port('[::]:50051')
server.start()
因為start 並不會block住你的process, 所以底下你可能需要一個sleep loop 頂在那裡 (看 examples/python/route_guide/route_guide_server.py)
Creating the client
你可以在這邊找到完整的 Client example
examples/python/route_guide/route_guide_client.py.
Creating a stub
要call service 的 methods 你需要先建一個 stub.
我們 會實例化一個 route_guide_pb2_grpc.RouteGuideStub
object 這是來自 route_guide_pb2_grpc(generated from .proto)
channel = grpc.insecure_channel('localhost:50051')
stub = route_guide_pb2_grpc.RouteGuideStub(channel)
Calling service methods
對於 RPC methods 回傳一個 single response (“response-unary” methods), gRPC Pytohn 支持 both synchronous (blocking) and asynchronous (non-blocking) 這兩種 control flow semantics.
對於 response-streaming RPC methods 會直接回傳 一個 response values 的 iterator. 當 call next()
method 時會 block 住直到 response 回來了
Simple RPC
一個 synchronous call simple RPC to GetFeature
就像是直接call 一個本地方法一樣, RPC 會等待server respond, 也會回傳值或 raise an exception
feature = stub.GetFeature(point)
一個 asynchronous call to GetFeature
也是類似, 但是 是像 call local method asynchronously in a thread pool
feature_future = stub.GetFeature.future(point)
feature = feature_future.result()
Response-streaming RPC
呼叫 response-streaming ListFeatures
就像是 處理sequence types:
for feature in stub.ListFeatures(rectangle);
Request-streaming RPC
呼叫 request-streaming RecordRoute
就像是 傳一個 iterator 到本地的method, 簡單的 RPC above 會傳一個 single response, 這可以被用 synchronously or asynchronously call.
route_summary = stub.RecordRoute(point_iterator)route_summary_future = stub.RecordRoute.future(point_iterator)
route_summary = route_summary_future.result()
Bidirectional streaming RPC
呼叫 bidirectionally-streaming RouteChat
是一個 request-streaming 跟response-streaming 的組合, 你可以像下面這樣傳 iterator進去 然後他回傳也是iterator.
for received_route_note in stub.RouteChat(sent_route_note_iterator):
Try it out!
把server 跑起來阿!, 聽 port 50051!
$ python route_guide_server.py
把Client 跑起來阿!! 用另外一個 terminal
$ python route_guide_client.py