[譯]gRPC Basics — Python

楊亮魯
15 min readMar 25, 2019

--

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 buffersOverview 也大概熟悉惹. 如果需要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

--

--