查看原文
其他

心法利器[6] | python grpc实践

机智的叉烧 CS的陋室 2022-08-24


【前沿重器】


全新栏目,本栏目主要和大家一起讨论近期自己学习的心得和体会,与大家一起成长。具体介绍:仓颉专项:飞机大炮我都会,利器心法我还有


往期回顾

完成一个算法任务后,总需要将服务打包上线,上线需要的一个人物,就是给包裹一层服务,对于我们常用的python而言,flask、tornado等都是比较常用的,今天给大家推荐的是一个比较新潮而且已经逐步推开使用的方案——grpc,这个东西其实我之间已经介绍过,并且给大家跑通了c++版本的demo,可能受限于语言,阅读量并不是特别好,对于它的基础知识,还是建议大家看下:

今天就来给大家分享这套方案,具体的demo代码来自:https://zhuanlan.zhihu.com/p/97893141,我已经跑通,我会再更详细地聊聊里面的内容。

grpc再介绍

grpc是一套谷歌开发的rpc框架https://github.com/grpc/grpc(相似的还有百度的brpc等https://github.com/apache/incubator-brpc/blob/master/README_cn.md),grpc提供了C、C++、Ruby、Python、Java等多种语言的接口,所以通用性很强,而且对于网络协议,其实不需要相同语言支持,例如python启动了一个grpc服务,Java只要按照proto约定的接口发出请求,那就可以收到并处理。

protobuf

protobuf也是谷歌出的(https://github.com/protocolbuffers/protobuf),是一种类似json、xml的结构,而相比这两个,其在效率、性能上有更高,作网络通信、数据交换有非常高效。

有关这块的语法,简单地可以通过这个教程来看看,其实就是比json格式多了一些数据类型的定义。

这里需要详细说的其实是有关grpc方面的数据类型。先来看grpc的官方例子——hello world任务的proto和核心部分:

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

最核心的其实就是service类型,这个类型的核心就在于我们需要定义服务,输入什么,输出什么。rpc本身代表了服务的类型,输入和输出的结构通常用message来表示,其实就是一个约束了数据类型的json格式了。

python构造grpc

环境

首先还是先把python的grpc环境构造起来:

pip install grpcio -i ${PIPSOURCE}
pip install grpcio-tools -i ${PIPSOURCE}
pip install protobuf -i ${PIPSOURCE}

PIPSOURCE是自己定义的pip源,设置国内的源,如阿里云、清华等,下载速度会加快很多。

proto

proto文件本身其实和python无关,只是一个和c++语法类似的配置文件。

下面的代码例子来自https://zhuanlan.zhihu.com/p/97893141,这个例子里实现了两个任务,一个是加法,一个是乘法,先来看看pb文件:

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;
}

输入是两个数,这里约束的都是32位整型。

然后就可以来编译proto文件为具体的python文件了:

 python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ./SimpleCal.proto

编译完以后就会多两个文件:

SimpleCal_pb2_grpc.py  SimpleCal_pb2.py

前者是针对服务的,即客户端类和服务端类,而后者则是针对request和response的,有兴趣可以help(XXX)一波看看概览。

服务端

有了上面的东西,就可以开始写自己的服务了,直接上代码:

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()

首先是这个服务CalServices他继承于SimpleCal_pb2_grpc的CalServicer,就需要定义里面的计算,其实这里的定义很简单,以加法为例,重点在这句:

return SimpleCal_pb2.ResultReply(number=request.number1 + request.number2)

这里写的很简单,其实就是直接拿request里面的两个东西进行计算罢了,这个ResultReply可以把对应的内容转化为pb完成回复。

这个类用于构造服务,服务是通过构造grpc.server后,在通过add_CalServicer_to_server把这个server对象加上,在配置端口后,服务就算启动了,核心就是这么几行:

server = grpc.server(futures.ThreadPoolExecutor(max_workers=5))
SimpleCal_pb2_grpc.add_CalServicer_to_server(CalServicer(),server)
server.add_insecure_port("[::]:50051")
server.start()

由于服务是一个类似监控的任务,是持续运行的,所以运行的时候,最好是使用nohup来执行:

nohup python cal_server.py &

如果执行成功,应该可以用ps -ef | grep cal_server来看这个任务还在不在。

完事了把这个服务kill调就行。

客户端

客户端是请求服务端获得结果的,所以简单地,可以只需要把pb中提到的请求内容请求出去就好了。

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(100300)

首先构造一个请求的通道,通过这个通道构造一个stub,这个东西其实和http中的client类似,然后就可以发送请求了,分别是加和乘。

这里面没有复杂的操作,其实就是整理好数据,然后把数据放入通道就行,其实就和函数一样,调用后就能得到结果了。这个执行就没那么复杂了。

python cal_client.py

小结

记录的是自己周末的学习,简单的走通了demo,后续要根据自己的需求修改pb,修改请求,尤其是服务端,工程化流程化。

趁着文章能写完,给大家分享一下,构造服务的能力也是算法工程师非常必要的一个能力,没有这个,你的算法根本无法上线。



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存