心法利器[6] | python grpc实践
【前沿重器】
全新栏目,本栏目主要和大家一起讨论近期自己学习的心得和体会,与大家一起成长。具体介绍:仓颉专项:飞机大炮我都会,利器心法我还有。
往期回顾
完成一个算法任务后,总需要将服务打包上线,上线需要的一个人物,就是给包裹一层服务,对于我们常用的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(100, 300)
首先构造一个请求的通道,通过这个通道构造一个stub,这个东西其实和http中的client类似,然后就可以发送请求了,分别是加和乘。
这里面没有复杂的操作,其实就是整理好数据,然后把数据放入通道就行,其实就和函数一样,调用后就能得到结果了。这个执行就没那么复杂了。
python cal_client.py
小结
记录的是自己周末的学习,简单的走通了demo,后续要根据自己的需求修改pb,修改请求,尤其是服务端,工程化流程化。
趁着文章能写完,给大家分享一下,构造服务的能力也是算法工程师非常必要的一个能力,没有这个,你的算法根本无法上线。