用 Python 編寫簡單的 gRPC 服務

作者:xnow.me
Blog: zhihu.com/people/xnow.me
個人感覺gRPC一直在流行與不流行之間,似乎周圍沒什么人用,但是每隔一段時間都會聽到一些gRPC的消息,今天剛好有團隊要gRPC的支持,所以就自己也測試下怎么用Python來寫gRPC的服務。RPC是遠程過程調用(Remote Procedure Call)的縮寫形式,可以理解為RPC就是要像調用本地的函數一樣去調遠程函數,gRPC就是Google開源的RPC框架。
這里寫個簡單的Python gRPC示例,能實現加法和乘法的計算器:
版本信息:
Python 3.6.8
grpcio 1.25.0
grpcio-tools 1.25.0
nginx version: nginx/1.14.0
開始環境准備
安裝gRPC相關的庫,grpcio-tools主要用根據我們的protocol buffer定義來生成Python代碼,官方解釋是Protobuf code generator for gRPC。protocolbuffers/protobuf是Google開發的一種序列化數據結構的協議。具體結構和語法超綱了,現在還不多用做太多理解,只要會用就行了。
$ sudo pip3 install grpcio grpcio-tools
定義服務:使用protocolbuffers/protobuf格式來創建結構化數據文件SimpleCal.proto,內容如下:
syntax = "proto3";
service Cal {
rpc Add(AddRequest) returns (ResultReply) {}
rpc Multiply(MultiplyRequest) returns (ResultReply) {}
}
message AddRequest {
int32 number1 = 1;
int32 number2 = 2;
}
message MultiplyRequest {
int32 number1 = 1;
int32 number2 = 2;
}
message ResultReply {
int32 number = 1;
}
在SimpleCal.proto 文件中定義了一個服務Cal,定義了2個RPC方法:Add和Multiply,需要分別在gRPC的服務端中實現加法和乘法。
同時我們也定義了2個方法的參數,Add方法的參數是AddRequest,包含number1和number2兩個整數參數。Multiply方法的參數是MultiplyRequest,里面也有number1和number2兩個整數參數。兩個函數的返回結構都是ResultReply,內容是一個整數。
根據上面的定義,生成Python代碼:
$ python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ./SimpleCal.proto
$ ls
SimpleCal_pb2_grpc.py SimpleCal_pb2.py SimpleCal.proto
使用python3 -m grpc_tools.protoc --hel能獲得命令的參數含義。ls可以看到grpc_tools 幫我們自動生成了 SimpleCal_pb2_grpc.py, SimpleCal_pb2.py這2個文件。這2個文件會在后面的客戶端和服務端代碼中被引用。
服務端和客戶端樣例
下面是服務端代碼 cal_server.py:
from concurrent import futures
import grpc
import SimpleCal_pb2
import SimpleCal_pb2_grpc
class CalServicer(SimpleCal_pb2_grpc.CalServicer):
def Add(self, request, context): # Add函數的實現邏輯
print("Add function called")
return SimpleCal_pb2.ResultReply(number=request.number1 + request.number2)
def Multiply(self, request, context): # Multiply函數的實現邏輯
print("Multiply service called")
return SimpleCal_pb2.ResultReply(number=request.number1 * request.number2)
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=5))
SimpleCal_pb2_grpc.add_CalServicer_to_server(CalServicer(),server)
server.add_insecure_port("[::]:50051")
server.start()
print("grpc server start...")
server.wait_for_termination()
if __name__ == '__main__':
serve()
這里的重點在於CalServicer類中對Add和Multiply兩個方法的實現。邏輯很簡單,從request中讀取number1和number2,然后相加。注意,這里的所有變量都需要完整名稱:request.number1和request.number2, 不能使用位置參數。Multiply 的實現和Add一樣,不多說了。serve函數里定義了gRPC的運行方式,使用5個worker的線程池。
客戶端代碼 cal_client.py :
import SimpleCal_pb2
import SimpleCal_pb2_grpc
import grpc
def run(n, m):
channel = grpc.insecure_channel('localhost:50051') # 連接上gRPC服務端
stub = SimpleCal_pb2_grpc.CalStub(channel)
response = stub.Add(SimpleCal_pb2.AddRequest(number1=n, number2=m)) # 執行計算命令
print(f"{n} + {m} = {response.number}")
response = stub.Multiply(SimpleCal_pb2.MultiplyRequest(number1=n, number2=m))
print(f"{n} * {m} = {response.number}")
if __name__ == "__main__":
run(100, 300)
客戶端的邏輯更加簡單,就連上gRPC服務,然后發起調用。下面開啟服務端,並執行客戶端代碼調用gRPC服務,結果如下:
$ python3 cal_server.py &
$ python3 cal_client.py
100 + 300 = 400
100 * 300 = 30000
執行結果表明客戶端和服務端已經都運行正常。更多的gRPC樣例可以訪問gRPC官網的Example, grpc/grpc 。
https://github.com/grpc/grpc/tree/master/examples/python
使用Nginx來代理gRPC
gRPC是基於HTTP/2協議的,Nginx在1.9.5里開始支持HTTP/2,在1.13.10里開始支持gRPC。為了反向代理gRPC服務,編譯Nginx的時候必須要添加這兩個參數:--with-http_ssl_module --with-http_v2_module
給Nginx添加如下的server配置:
server {
listen 80 http2;
location / {
grpc_pass grpc://localhost:50051;
}
}
把這段server的配置添加到Nginx的http段里,配置和啟動好Nginx之后,然后把cal_client.py里的channel = grpc.insecure_channel('localhost:50051') 一行的連接地址替換為Nginx提供的地址就可以了。執行結果是一樣的,就不再做一遍了。
接着往下挖掘gRPC的HTTP2.0接口細節的話,可以打開SimpleCal_pb2_grpc.py你可以看到在CalStub這個類的__init__方法里,定義了Add和Multiply兩個函數對應的uri。
class CalStub(object):
# missing associated documentation comment in .proto file
pass
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.Add = channel.unary_unary(
'/Cal/Add', # 這個是對應Add方法的http url地址
request_serializer=SimpleCal__pb2.AddRequest.SerializeToString,
response_deserializer=SimpleCal__pb2.ResultReply.FromString,
)
self.Multiply = channel.unary_unary(
'/Cal/Multiply', # 這個是對應Multiply方法的http url地址
request_serializer=SimpleCal__pb2.MultiplyRequest.SerializeToString,
response_deserializer=SimpleCal__pb2.ResultReply.FromString,
)
查看Nginx的日志也能表明這一點:
127.0.0.1 - - [18/Nov/2019:20:09:25 +0800] "POST /Cal/Add HTTP/2.0" 200 8 "-" "grpc-python/1.25.0 grpc-c/8.0.0 (manylinux; chttp2; game)"
127.0.0.1 - - [18/Nov/2019:20:09:25 +0800] "POST /Cal/Multiply HTTP/2.0" 200 9 "-" "grpc-python/1.25.0 grpc-c/8.0.0 (manylinux; chttp2; game)"
如果部署了多個gRPC服務端,也可以使用Nginx的upstream來做多個后端的負載均衡。
最后,用wireshark來對http2的流量進行抓包分析。

抓取HTTP2的數據包進行gRPC協議分析
參考文章:
Introducing gRPC Support with NGINX 1.13.10 - NGINX
https://www.nginx.com/blog/nginx-1-13-10-grpc/
gRPC 官方文檔中文版_V1.0
https://doc.oschina.net/grpc?t=60138
grpc/grpc
https://github.com/grpc/grpc/tree/master/examples/python
▼
