昔我往矣

Python编写简单的gRPC服务

2019年11月18日

感觉gRPC似乎一直在流行与不流行之间,似乎周围没什么人用,但是每隔一段时间都会听到一些gRPC的消息,今天刚好有团队要gRPC的支持,所以就自己也测试下怎么用Python来写gRPC的服务。RPC是远程过程调用(Remote Procedure Call)的缩写形式,基本可以理解为RPC就是要像调用本地的函数一样去调远程函数,gRPC就是Google开源的RPC框架。闲话不多说,show you the code!

gRPC

这里写个简单的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代码:

$ sudo pip3 install grpcio grpcio-tools

定义服务:使用Protocol Buffers格式来创建结构化数据文件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方法:AddMultiply,分别在服务端中实现加法和乘法。
同时我们也定义了2个方法的参数,Add方法的参数是AddRequest,包含number1和number2两个整数参数。 Multiply方法的参数是MultiplyRequest,里面也有number1number2两个整数参数。两个函数的返回结构都是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

可以看到grpc_tools帮我们自动生成了 SimpleCal_pb2_grpc.pySimpleCal_pb2.py2个文件。这两个文件会在后面的客户端和服务端中被引用。

服务端和客户端

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类中对AddMultiply两个方法的实现。逻辑很简单,从request中读取number1和number2,然后相加。注意,这里的所有变量都需要完整名称:number,request.number1和request.number2, 不能使用位置参数。Multiply 的实现和Add一样,不多说了。 serve函数里定义了gRPC的运行方式。

客户端代码:

import SimpleCal_pb2
import SimpleCal_pb2_grpc
import grpc


def run(n, m):
  channel = grpc.insecure_channel('localhost:50051')
  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服务,然后发起调用。

下面开启服务端和客户端执行,结果如下:

$ python3 cal_server.py  &
$ python3 cal_client.py 
100 + 300 = 400
100 * 300 = 30000

表明客户端和服务端已经都运行正常。

更多的gRPC样例可以访问gRPC官网的examples

使用Nginx来代理gRPC

gRPC是基于HTTP/2协议的,Nginx在1.9.5里开始支持HTTP/2,在1.13.10里开始支持gRPC。编译Nginx的时候必须要添加这两个参数:--with-http_ssl_module --with-http_v2_module

nginx添加如下的server配置:

server {
    listen 80 http2;
    server_name g.xnow.me;

    location / {
      grpc_pass grpc://localhost:50051;
    }
  }

然后把cal_client.py里的channel = grpc.insecure_channel('localhost:50051') 一行的连接地址替换为Nginx提供的地址就可以了。结果是一样的,就不再做一遍了。

其实,打开SimpleCal_pb2_grpc.py你可以看到在CalStub这个类的__init__方法里,定义了AddMultiply两个函数对应的URL。看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的流量进行抓包分析。

wireshark抓gRPC包

参考文章:
https://www.nginx.com/blog/nginx-1-13-10-grpc/
https://doc.oschina.net/grpc?t=60138
https://github.com/grpc/grpc/tree/master/examples/python

仅有一条评论 »

  1. Grey 1. Grey December 13th, 2019 at 02:07 pm | Reply

    写的很好,博主加油

添加新评论 »