更详细版本戳-->https://rabbitmq.mr-ping.com/tutorials_with_python/[6]RPC.html
关于同步调用、回调、异步调用可以康康这篇文章:https://blog.csdn.net/mayue_web/article/details/88956222
= 同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;
- 回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;
- 异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口);
远程过程调用RPC
RPC:将一个函数运行在远程计算机上并且等待从那儿获取结果
这篇文章提到的rabbitmq搭建的rpc系统:包括一个客户端和一个服务器。这里创建一个模拟RPC服务返回fibonacci数列
客户端接口
为展示RPC服务如何使用,这里创建了一个简单的客户端类。暴露出一个名为"call"的方法来发送一个请求,并且在收到回应前保持阻塞
fibonacci_rpc = FibonacciRpcClient()
result = fibonacci_rpc.call(4)
print "fib(4) is %r" % (result,)
回调队列
一般来说通过rabbitmq来实现RPC是容易的。一个客户端发送请求信息,服务器将其应用到一个回复信息中。为了接受到回复信息,客户端需要在发送请求的时候同时发送一个回调队列(callback queue)的地址。
result = channel.queue_declare(exclusive=True)
callback_queue = result.method.queue
channel.basic_publish(exchange='',
routing_key='rpc_queue',
properties=pika.BasicProperties(
reply_to = callback_queue,
),
body=request)
# ... and some code to read a response message from the callback_queue ...
消息属性
AMQP协议给消息预定义了一系列的14个属性。大多数属性很少会用到,除了以下几个:
- delivery_mode(投递模式):将消息标记为持久的(值为2)或暂存的(除了2之外的其他任何值)。第二篇教程里接触过这个属性,记得吧?
- content_type(内容类型):用来描述编码的mime-type。例如在实际使用中常常使用application/json来描述JOSN编码类型。
- reply_to(回复目标):通常用来命名回调队列。
- correlation_id(关联标识):用来将RPC的响应和请求关联起来。
关联表示
为每个客户端只建立一个独立的回调队列。但是问题在于队列接收到一个响应的时候它无法判断这个响应属于哪一个请求-->由此引入correlation_id:为每一个请求设置一个独一无二的值。当我们从回调队列接收到一个信息的时候,我们可以查看这条属性从而将响应和请求匹配起来。如果我们接受到的消息的correlation_id是未知的,就直接销毁掉它,因为它不属于任何一条请求。
总结
我们的RPC如此工作:
- 当客户端启动的时候,它创建一个匿名独享的回调队列。
- 在RPC请求中,客户端发送带有两个属性的消息:一个是设置回调队列的 reply_to 属性,另一个是设置唯一值的 correlation_id 属性。
- 将请求发送到一个 rpc_queue 队列中。
- RPC工作者(又名:服务器)等待请求发送到这个队列中来。当请求出现的时候,它执行他的工作并且将带有执行结果的消息发送给reply_to字段指定的队列。
- 客户端等待回调队列里的数据。当有消息出现的时候,它会检查correlation_id属性。如果此属性的值与请求匹配,将它返回给应用。
rpc_client.py
#!/usr/bin/env python
import pika
import uuid
class FibonacciRpcClient(object):
#建立连接,通道并且为回复(replies)声明独享的回调队列。
def __init__(self):
self.connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
self.channel = self.connection.channel()
result = self.channel.queue_declare(queue='', exclusive=True)
self.callback_queue = result.method.queue
#订阅这个回调队列,以便接受RPC的响应
self.channel.basic_consume(
queue=self.callback_queue,
on_message_callback=self.on_response,
auto_ack=True)
#对每一个响应执行一个非常简单的操作,检查每一个响应消息的correlation_id属性是否与我们期待的一直,如果一致,将响应结果赋给self.response,然后跳出consuming循环
def on_response(self, ch, method, props, body):
if self.corr_id == props.correlation_id:
self.response = body
#主要方法call方法,执行真正的RPC请求
def call(self, n):
self.response = None
self.corr_id = str(uuid.uuid4())#生成一个唯一的correlation_id值并且保存起来,'on_response'回调函数会用她来获取符合要求的响应
self.channel.basic_publish(#发布带有reply_to和correlaiton_id属性的消息
exchange='',
routing_key='rpc_queue',
properties=pika.BasicProperties(
reply_to=self.callback_queue,
correlation_id=self.corr_id,
),
body=str(n))
while self.response is None:
self.connection.process_data_events()
return int(self.response)
fibonacci_rpc = FibonacciRpcClient()
print(" [x] Requesting fib(30)")
response = fibonacci_rpc.call(30)#将响应返回给用户
print(" [.] Got %r" % response)
rpc_server.py
#!/usr/bin/env python
import pika
#建立连接,声明队列
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='rpc_queue')
#声明fibonacci函数,假设只有合法的正整数作为输入(不能处理过大的数字,这里可能溢出
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n - 1) + fib(n - 2)
#为basic_comsume声明一个回调函数,这是RPC服务器端的核心。它执行实际的操作并且作出响应
def on_request(ch, method, props, body):
n = int(body)
print(" [.] fib(%s)" % n)
response = fib(n)
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)
#服务器可以多开几个线程。为了能负载平均地分摊到多个服务器,需要设置好prefetch_count
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='rpc_queue', on_message_callback=on_request)
print(" [x] Awaiting RPC requests")
channel.start_consuming()
运行结果:
说明:可以看到如果启用多个rpc_server.py,会轮流响应rpc_client.py的请求