rpc中文即远程过程调用的意思
微服务架构大行其道的形势下, 作为开发人员或多或少都会接触到相关技术
大厂google的gRPC、Facebook的thrift、阿里的dubble算是rpc框架中比较优秀的代表啦
其中不少框架都是支持跨语言调用,有python调用接口,但是因为用其他语言编写,使用起来略显复杂,且不太pythonic 手动滑稽
这里不妨借助rabbitMQ 实现一个自己的简单rpc框架
小伙伴们首先得熟悉一下rabbitMQ的api, 这里贴出一个非常友好的教程文档,文档不大仅有几篇文章,快一点大概半天即可了解消息队列的机制,rabbitmq作为生产中经常用到的工具,需要好生了解哦~
python版教程最后一篇,就是一个简单的rpc demo
该实现仅能作单个函数的远程调用,我在此之上重新封装实现了一个支持多种函数调用的rpc实现
服务架设配置:
系统 | 版本 |
---|---|
ubuntu | 16.04 |
RabbitMQ | 3.5.7 |
python | 3.6.2 |
pika | 1.0.0 |
客户端/调用端部分:
在初始化对象方法中加入了两个数据结构:
# 请求id结果映射表
self._req_id_result_map = {}
# 请求id列表
self.req_id_list = []
每一次请求调用都需要使用一个唯一标识id
这两个数据结构,一个用来保存调用结果,一个用来保存调用者id列表
rst = self._req_id_result_map.get(req_id, None)
del self._req_id_result_map[req_id]
从保存的结果中拿出数据,记得要清空数据,不然请求数据量大的话会出现数据积压到内存从而导致内存溢出的情况
def call(self, req_id, fn, param):
"""
:param req_id:str 远程调用请求id 要求唯一
:param fn:str 远程调用函数名 要求远端必须有同名函数
:param param:list 远程调用参数 限制列表长度为2 第一个元素代表位置参数的列表
无参数传空列表 第二个元素为具名参数的字典
没有则传空字典 远端调用类似 fn(*param[0], **param[1])
:return:
"""
self.req_id_list.append(req_id)
self.channel.basic_publish(exchange='', routing_key='rpc_queue',
properties=pika.BasicProperties(reply_to=self.callback_queue,
correlation_id=req_id),
body=json.dumps([fn, param]))
while not self._req_id_result_map.get(req_id, None):
self.connection.process_data_events()
return self.get_request_result(req_id)
这里是调用端的核心逻辑:
call方法参数为 请求id, 调用函数名, 调用函数参数
其中参数有特别要求,必须是长度为二的可遍历容器(列表、元祖、set...)
容器的第一个元素代表位置参数,第二个元素是命名参数的字典
call方法首先向默认交换机发送数据,注意这里通过路由键把消息指定到名为rpc_queue
的队列中, 然后阻塞一直等到远端响应结果
服务端/被调端部分:
服务端要准备好远程调用的函数,建议模块化到单独的地方然后引入到服务端主逻辑模块
声明connection、rpc_queue
队列、绑定回调函数并开启消费(consume)
回调函数的主要逻辑:
def on_request(ch, method, props, body):
fn, param = json.loads(body.decode())
print(" [.] fib(%s)" % (param,))
if not (isinstance(param, list) and len(param) == 2 and
isinstance(param[0], list) and isinstance(param[1], dict)):
response = "error params please use like this fn(*param[0], **param[1])"
else:
try:
fn = eval(fn)
response = fn(*param[0], **param[1])
except:
response = "remote has not function like {}".format(fn)
ch.basic_publish(exchange='', routing_key=props.reply_to,
properties=pika.BasicProperties(correlation_id=props.correlation_id),
body=str(response))
ch.basic_ack(delivery_tag=method.delivery_tag)
这里要判断调用端的参数格式正确与否,以及调用的函数是否存在
取出调用端的reply_to
作为路由键(也是调用端声明的队列,注意有两个队列,一个存放远程调用信息,一个存放远程调用结果, 分别在服务端/客户端声明)
同时取出correlation_id
这个即是调用端的请求id 把这个id作为属性发送到调用端,调用端通过这个id(可以看成一个token)取出调用结果
数据传输默认使用二进制, 参数部分先json序列化,然后转化为二进制
最后把调用端封装了一下方法:
call_single、call_many
使用这个两个函数即可单次、多次进行rpc
调用端(客户端)和远程端(服务端/被调端)分别部署在不同的机器上,注意配置统一的rabbitMQ主机地址,至于消息队列可以放在服务端也可以放在客户端甚至可以部署在一台独立的服务器上
最后总结:
源代码地址在此哦 >~<
这里用最简洁直接的方式实现了一个rpc框架,目前仅支持函数的远程调用,后续可以加入方法的远程调用,原理也和函数调用类似,感兴趣的小伙伴可以自己动手实现一下