SpringBoot11-基于ThriftRPC调用Python的模型服务+设计模式 首先RPC在开发这边是一个很重点的内容,目前浅显的理解来说,RPC相比HTTP服务的区别在于RPC有自己的协议栈,而且不会像外部暴露端口,一般是一个系统内部调用的时候使用RPC的方式,而且应该来说是有更快速的通信速度
本文使用Thrift这一款RPC框架,RPC有许多种框架,其中Thrift是比较常用的一种框架爱
而设计模式也是java开发和所有开发中的一种重点内容,这里的如果要使用Java调用Python的模型服务的话,不可能让ThriftClinet每次都新建连接,这样的开销实在太大了,所以应该是先建立好连接后,一直复用这个连接,这也引出了设计模式的概念
1. Thrift的安装 对于Thrift安装,可以直接使用brew安装,但是要注意一下安装版本的问题,因为使用Java的JDK版本还有SpringBoot版本之间的匹配,这里Thrift最终选用了0.9.3这个版本。arm架构下homebrew安装请参考这篇博客:https://yixuan004.github.io/2023/06/07/mixed/linuxmacos/macOS%E5%8F%8ALinux-%E5%9C%A8arm%E6%9E%B6%E6%9E%84mac%E8%8A%AF%E7%89%87%E4%B8%8A%E5%AE%89%E8%A3%85homebrew/
安装后,可能会有如下提示,需要添加一些环境变量的内容
1 2 3 4 5 6 7 8 9 10 11 12 thrift@0.9 is keg-only, which means it was not symlinked into /usr/local, because this is an alternate version of another formula.If you need to have thrift@0.9 first in your PATH, run: echo 'export PATH="/usr/local/opt/thrift@0.9/bin:$PATH"' >> ~/.zshrcFor compilers to find thrift@0.9 you may need to set: export LDFLAGS ="-L/usr/local/opt/thrift@0.9/lib" export CPPFLAGS ="-I/usr/local/opt/thrift@0.9/include" For pkg-config to find thrift@0.9 you may need to set: export PKG_CONFIG_PATH ="/usr/local/opt/thrift@0.9/lib/pkgconfig"
查看安装后的版本
1 2 (base) ➜ /Users/curious ✻ thrift -version [22:11:48] Thrift version 0.9.3
2. 对Java/Python一个基础”代码“的初始化 需要首先照猫画虎编写一个ClassifyService.thrift
,简单描述一下这个文件,就是定义了一个java和一个python的namespace,表示生成的文件应该放在什么位置下。然后定一个两个方法的接口,一个是ping就是心跳检测这样的,另外一个就是用来进行文本分类的(这里简化了一下,就只有一个name作为输入,实际上还应该带上模型版本号什么的)
1 2 3 4 5 6 7 namespace java cn.edu .bupt .aiswitchboard .thrift namespace py aiswitchboard.thrift service ClassifyService { bool ping (), i32 classify (1 : string name) }
这个初始化的代码可以放在SpringBoot项目的这个路径下,方便之后推git
1 aiswitchboard/src/thrift/ClassifyService.thrift
生成这个基础代码后,使用brew安装的thrift进行代码的生成
1 2 thrift --gen py ClassifyService.thrift thrift --gen java ClassifyService.thrift
会在目录下生成java和python的代码,目录结构如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 . ├── __init__.py └── aiswitchboard ├── __init__.py ├── __pycache__ │ └── __init__.cpython-36.pyc └── thrift ├── ClassifyService-remote ├── ClassifyService.py ├── __init__.py ├── __pycache__ │ ├── ClassifyService.cpython-36.pyc │ ├── __init__.cpython-36.pyc │ └── ttypes.cpython-36.pyc ├── constants.py └── ttypes.py └── cn └── edu └── bupt └── aiswitchboard └── thrift └── ClassifyService.java
3. 启动 + 整合进入SpringBoot项目中 3.1 Python启动 启动python这里的代码,我这里thrift版本使用的是0.9.3的版本,可能python会报错一个找不到ttypes的错误,开始还以为ttypes是一个外面的库,后来发现是自己这里的,应该是有一个import改成import .ttypes就可以了
python的启动还需要自己写一个ServiceHandler,然后把生成py的目录给append进环境里面来
这里踩了一个坑是127.0.0.1和localhost的区别,可能一些场景下还是尽量避免DNS解析,写127.0.0.1比localhost好吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import sys sys.path.append('/Users/curious/Desktop/thriftinstall/gen_py' )from thrift.transport import TSocketfrom thrift.transport import TTransportfrom thrift.protocol import TBinaryProtocolfrom thrift.server import TServerfrom aiswitchboard.thrift import ClassifyServiceclass ClassifyServiceHandler : def ping (self ): return True def classify (self, content ): return 1 if __name__ == '__main__' : handler = ClassifyServiceHandler() processor = ClassifyService.Processor(handler) transport = TSocket.TServerSocket('127.0.0.1' , 9744 ) tfactory = TTransport.TBufferedTransportFactory() pfactory = TBinaryProtocol.TBinaryProtocolFactory() server = TServer.TSimpleServer(processor, transport, tfactory, pfactory) print ("Starting the Python server..." ) server.serve() print ("Python server stopped." )
3.2 整合进入SpringBoot项目中 生成的ClassifyService类可以放到thrift目录下,这个thrift目录和其他java目录是平级的,比如controller,service那些的
1 aiswitchboard/src/main/java/cn/edu/bupt/aiswitchboard/thrift/ClassifyService.java
然后编写一个ThriftClient类,放在和controller service那些相同的目录下
aiswitchboard/src/main/java/cn/edu/bupt/aiswitchboard/client/ThriftClient.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package cn.edu.bupt.aiswitchboard.client;import cn.edu.bupt.aiswitchboard.thrift.ClassifyService;import org.apache.thrift.protocol.TBinaryProtocol;import org.apache.thrift.protocol.TProtocol;import org.apache.thrift.transport.TSocket;import org.apache.thrift.transport.TTransport;import org.springframework.stereotype.Component;@Component public class ThriftClient { private String host; private Integer port; public ThriftClient () { } public ThriftClient (String host, Integer port) { this .host = host; this .port = port; } public void callPythonService (String sceneName, String text) { try { TTransport transport = new TSocket (host, port); transport.open(); TProtocol protocol = new TBinaryProtocol (transport); ClassifyService.Client client = new ClassifyService .Client(protocol); boolean pingResult = client.ping(); System.out.println("Ping result: " + pingResult); int classifyResult = client.classify(text); System.out.println("classifyResult result: " + classifyResult); transport.close(); } catch (Exception e) { e.printStackTrace(); } } }
由于需要反复使用这个ThriftClient,故使用单例模式进行初始化,避免反复初始化,这个写在的位置是utils文件夹里面
aiswitchboard/src/main/java/cn/edu/bupt/aiswitchboard/utils/ThriftClientSingleton.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package cn.edu.bupt.aiswitchboard.utils;import cn.edu.bupt.aiswitchboard.client.ThriftClient;public class ThriftClientSingleton { private static ThriftClient instance; private ThriftClientSingleton () { instance = new ThriftClient ("127.0.0.1" , 9744 ); } public static ThriftClient getInstance () { if (instance == null ) { synchronized (ThriftClientSingleton.class) { if (instance == null ) { new ThriftClientSingleton (); } } } return instance; } }
3.3 在SpringBoot项目中调用 单例模式
1 ThriftClientSingleton.getInstance().callPythonService(sceneName, text);