最近做的一个项目,希望将前端接口调用与后台的算法分离,当算法进行升级更新的时候,前端无需调整,为此,计划使用pcr框架来实现二者的通信。
1. PCR结构
如图所示,灰色框部分由pcr的框架进行实现。
2. rpyc示例
python可以通过如下方式实现rpyc框架。安装rpyc:pip3 install rpyc==5.0.1
client.py,客户端:
import rpyc
from threading import Thread
import random
def call_prc(i):
print(i)
a = random.randint(0, 1000)
b = random.randint(0, 1000)
result = conn.root.sum(i, a, b)
print('>>>', i, a, b, result)
conn = rpyc.connect('IP', 9999)
# 多线程,同时请求10次
exist_thread_array = []
for i in range(10):
t1 = Thread(None, call_prc, None, args=(i,))
t1.start()
exist_thread_array.append(t1)
for sub_thread in exist_thread_array:
sub_thread.join()
conn.close()
server.py,服务器端:
from rpyc import Service
from rpyc.utils.server import ThreadedServer
import random
import time
class TestService(Service):
# 如果名字是以 “exposed_” 开始的, 这个属性将可以被远程访问, 否则只能在本地进行访问
def exposed_sum(self, num0: int, num1: int, num2: int) -> int:
'''
实现num1、num2相加
:param num1:
:param num2:
:return:
'''
a = random.randint(0, 5)
print('request: %s, sleep: %s' % (num0, a))
time.sleep(a)
return num1 + num2
if __name__ == '__main__':
# 如果想要让客户端访问未暴露的方法,需要再ThreadedServer中配置:protocol_config={"allow_public_attrs": True})
s = ThreadedServer(TestService, port=9999, auto_register=False)
s.start()
从客户端与服务器端日志可以发现,在同一个进程里向服务器发送请求,rpc一次收到这10个请求后,是以排队的方式执行任务的。当第1个任务执行完毕后,再执行第0、5、7…的任务。
之后,我开了3个进程,3个进程同时发送10次请求,rpc接收到这30次请求的日志如图,可以发现,rpc会同时处理3个进程的请求,但是进程内部的10次请求是排队的方式执行的。
3. 问题处理
3.1 响应超时
调用一个计算时间较长的接口时,报错TimeoutError: result expired,即响应超时,因此,配置如下:
# 设置响应时间最长为240s
# 可以设置为None
conn._config['sync_request_timeout'] = 240
3.2 AttributeError: cannot access ‘extend’
调用接口处理,其中涉及list的extend方法调用,报错’extend’找不到,觉得很奇怪,首先检查输入的2个参数确实为list,本地可以执行,百度后提示:rpc的包版本不一致,但是检查之后发现版本均为5.0.1。
考虑到输入输出都是较为复杂的dict,因此采用json进行编码和解码,成功执行
# 客户端输入
result = conn.root.sum(json.dumps(params))
# 服务器端返回
json.dumps(results, ensure_ascii=False)
这是由于rpc会对接收和返回的数据进行包装,将其转换为包装类型:<netref class ‘rpyc.core.netref.type’>
为了避免json的序列化开销,也可以使用下列方法将包装类型转为基本类型:
客户端:
conn = rpyc.connect(
ip, port, config={"allow_public_attrs": True, "allow_pickle": True})
data = conn.root.process_edi_risk(params)
# 将rpc的包装类:<netref class 'rpyc.core.netref.type'>,转为基本类型:dict
data = rpyc.classic.obtain(data)
服务端:
rpyc.core.protocol.DEFAULT_CONFIG['allow_pickle'] = True
params= rpyc.classic.obtain(params)
s = ThreadedServer(
ExpressService, port=config_map['rpc_port'], auto_register=False,
protocol_config=rpyc.core.protocol.DEFAULT_CONFIG)
4. grpc
python还可以通过grpc实现rpc框架
- 编写proto文件并编译,生成2个文件
python -m grpc_tools.protoc -I./ --python_out=. --grpc_python_out=. time.proto
syntax = "proto3";
package time;
service Time { // Time 服务名
// GetTime RPC 调用
// TimeRequest RPC 输入类型
// TimeReply RPC 输出类型
rpc GetTime (TimeRequest) returns (TimeReply) {}
}
// Empty Request Message
message TimeRequest {
}
// The response message containing the time
message TimeReply {
string message = 1; // 字符串类型
}
- _pb2.py:包含生成的 request(HelloRequest) 和 response(HelloReply) 类。
- _pb2_grpc.py:包含生成的 客户端(GreeterStub)和服务端(GreeterServicer)的类