用 Python 編寫簡單的 gRPC 服務


用 Python 編寫簡單的 gRPC 服務

xnow.me Python中文社區 4天前


作者: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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM