社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

用 Python 编写简单的 gRPC 服务

Python中文社区 • 5 年前 • 588 次点击  



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


▼点击成为社区注册会员      喜欢文章,点个在看

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/52280
 
588 次点击