背景
python作为一种胶水语言,天然的是很好的中间件的语言首选。python拥有强大的兼容性,尤其是在科学计算领域,可以灵活的调用c++的dll库,调用pytorch和tensorflow等python算法框架。因此在我的项目中,使用python3.7作为算法中间件的实现语言,集成各类算法,对外提供服务。当然,这种方案也是有弊端的,就是python的性能,毕竟不如c++,但是相比较综合的使用成本,是优于c++的。
参考资料
https://www.zhihu.com/question/41609070/answer/1030913797 既然有 HTTP 请求,为什么还要用 RPC 调用? - 易哥的回答 - 知乎
https://zhuanlan.zhihu.com/p/36427583 如何给老婆解释什么是RPC - 柳树的文章 - 知乎
https://www.grpc.io/ grpc官网
https://developers.google.com/protocol-buffers protobuf官网
https://blog.csdn.net/qq_25310669/article/details/120651472 java实现grpc,与这一篇是对应的。
https://www.grpc.io/docs/languages/python/quickstart/ python的快速开始
案例与解决方案
1.安装grpc相关的依赖、代码案例
1.先决条件:
Python 3.5 或更高版本
pip 9.0.1 或更高版本
2.安装grpcio,protobuf
python -m pip install grpcio
python -m pip install protobuf
3.安装grpcio-tools,用于基于proto文件生成python代码。
python -m pip install grpcio-tools
4.克隆一下案例代码进行学习
$ git clone -b v1.41.0 https://github.com/grpc/grpc
# Navigate to the "hello, world" Python example:
$ cd grpc/examples/python/helloworld
2.设计proto文件
此处使用博主这篇博文的proto文件的内容(https://blog.csdn.net/qq_25310669/article/details/120651472),proto文件内容如下:
syntax = "proto3";
option java_multiple_files = false; //不要拆分成多个文件
option java_package = "com.rpc.yolo";
option java_outer_classname = "YoloProto";
// yolo模型对外提供的接口
service YoloFun {
// 初始化
rpc init (InitRequest) returns (InitReply) {}
// 检测
rpc detection (DetectionRequest) returns (DetectionReply) {}
// 模拟方法
rpc free (FreeRequest) returns (FreeReply) {}
}
// 初始化方法发送对象
message InitRequest {
string id = 1;
string cfg = 2;
string data = 3;
string weights= 4;
}
// 初始化方法反馈对象
message InitReply {
string result = 1;
}
// 检测方法发送对象
message DetectionRequest {
string id = 1; //模型编号
string path = 2; //原图的文件路径 /usr/img/1.jpg
string detect_file_path = 3; //识别后的文件路径 /usr/local/1.jpg
}
//怕掉精度,直接存string
message Box{
string x = 1;
string y = 2;
string w = 3;
string h = 4;
string obj = 5;
string prob = 6;
}
// 检测方法反馈对象
message DetectionReply {
string msg = 1;
repeated Box boxes = 2;
}
// 释放方法发送对象
message FreeRequest {
string id = 1;
}
// 释放方法反馈对象
message FreeReply {
string result = 1;
}
3.基于proto,生成python代码
管理员权限打开终端,切到proto文件所在的路径,然后执行protoc命令对文件进行编译,生成代码:
python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. yolo.proto
yolo.proto 是文件名,输出到当前目录。
然后将生成的文件拷贝到需要的目录就可以使用了。生成的文件截图如下:
yolo_pb2:基本是参数相关的在里面。
yolo_pb2_grpc:是接口相关的在里面。
使用的时候按照这个逻辑去做就ok了。
4.python实现rpc服务
首先对暴露的接口进行具体的实现,具体的语法可以看quick-start,案例如下:
"""
[yolo模型对外暴露的方法]
"""
from . import yolo_pb2
from . import yolo_pb2_grpc
import logging
import grpc
from . import darknet_images
class Yolo(yolo_pb2_grpc.YoloFunServicer):
async def init(
self, request: yolo_pb2.InitRequest,
context: grpc.aio.ServicerContext) -> yolo_pb2.InitReply:
"""[初始化模型]
Args:
request (yolo_pb2.InitRequest): [初始化请求]
context (grpc.aio.ServicerContext): [上下文]
Returns:
yolo_pb2.InitReply: [初始化结果]
"""
logging.debug('Init~~')
# 异常
try:
darknet_images.init_network(request.id, request.cfg, request.data, request.weights)
except BaseException:
logging.error('初始化发生了异常!')
return yolo_pb2.InitReply(result='error')
return yolo_pb2.InitReply(result='success')
async def detection(
self, request: yolo_pb2.DetectionRequest,
context: grpc.aio.ServicerContext) -> yolo_pb2.DetectionReply:
logging.debug('Detection~~')
detections = darknet_images.detection_img(request.id, request.path,request.detect_file_path)
detectionReply = yolo_pb2.DetectionReply()
# 判断非空
if detections == None:
# box = detectionReply.boxes.add()
# box.obj = str('')
# box.prob = str('')
# box.x = str('')
# box.y = str('')
# box.w = str('')
# box.h = str('')
# none表示未初始化
detectionReply.msg = 'none'
return detectionReply
for item in detections:
box = detectionReply.boxes.add()
box.obj = str(item[0])
box.prob = str(item[1])
box.x = str(item[2][0])
box.y = str(item[2][1])
box.w = str(item[2][2])
box.h = str(item[2][3])
return detectionReply
async def free(
self, request: yolo_pb2.FreeRequest,
context: grpc.aio.ServicerContext) -> yolo_pb2.FreeReply:
"""释放模型
Args:
request (yolo_pb2.FreeRequest): [请求内容,含有id]
context (grpc.aio.ServicerContext): [上下文]
Returns:
yolo_pb2.FreeReply: [释放结果]
"""
logging.debug('Free,模型编号:' + request.id)
# 异常
try:
darknet_images.free_network(request.id)
except BaseException:
logging.error('释放模型发生了异常!')
return yolo_pb2.FreeReply(result='error')
logging.debug('模型释放成功!')
return yolo_pb2.FreeReply(result='success')
5.服务对外暴露
有了接口代码,还需要把接口暴露出去。一般在这个地方可以集成多个rpc服务,不只是一个rpc服务文件的暴露。
"""python算法中间件启动主程序"""
import sys
import os
sys.path.append(os.path.dirname(__file__) + "/AlgorithmsRepo/yolo")
from AlgorithmsRepo.yolo.yolo_service import Yolo
import logging
import asyncio
import grpc
# from AlgorithmsRepo.yolo import yolo_pb2_grpc
import AlgorithmsRepo.yolo.yolo_pb2_grpc as yolo_pb2_grpc
async def serve() -> None:
server = grpc.aio.server()
#在这里添加对外公布的接口类
yolo_pb2_grpc.add_YoloFunServicer_to_server(Yolo(), server)
listen_addr = '[::]:22972'
server.add_insecure_port(listen_addr)
logging.info("Starting server on %s", listen_addr)
await server.start()
try:
await server.wait_for_termination()
except KeyboardInterrupt:
# Shuts down the server with 0 seconds of grace period. During the
# grace period, the server won't accept new connections and allow
# existing RPCs to continue within the grace period.
await server.stop(0)
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
asyncio.run(serve())