python3 RabbitMQ ( RPC!)

What This Tutorial Focuses On

在第二个教程中,我们学习了如何使用工作队列在多个工作者之间分配耗时的任务。

但是,如果我们需要在远程计算机上运行一个函数并等待结果呢?那是另一回事了。这种模式通常称为远程过程调用或RPC。

在本教程中,我们将使用RabbitMQ构建RPC系统:客户机和可伸缩RPC服务器。由于我们没有任何值得分发的耗时任务,我们将创建一个返回斐波那契数的虚拟RPC服务。

Client interface

为了说明如何使用RPC服务,我们将创建一个简单的客户端类。它将公开一个名为call的方法,该方法发送RPC请求并阻塞,直到接收到答案:

fibonacci_rpc = FibonacciRpcClient()
result = fibonacci_rpc.call(4)
print("fib(4) is %r" % result)

A note on RPC
虽然RPC在计算中是一种非常常见的模式,但它经常受到批评。当程序员不知道函数调用是本地调用还是慢RPC时,问题就出现了。这样的混乱会导致不可预测的系统,并增加不必要的调试复杂性。滥用RPC可能导致无法维护的意大利面条式代码,而不是简化软件。

记住这一点,考虑以下建议:
确保哪个函数调用是本地的,哪个函数调用是远程的。
文件系统。明确组件之间的依赖关系。
处理错误情况。当RPC服务器长时间宕机时,客户机应该如何反应?

在有疑问时避免RPC。如果可以,您应该使用异步管道——而不是像rpc那样阻塞,结果将异步推到下一个计算阶段。

Callback queue

一般来说,在RabbitMQ上执行RPC很容易。客户端发送请求消息,服务器使用响应消息进行应答。为了接收响应,客户端需要与请求一起发送一个“回调”队列地址。让我们试一试:

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 0-9-1协议预先定义了一组与消息相关的14个属性。除下列情况外,大部分物业很少使用:

1.delivery_mode:将消息标记为持久化(值为2)或瞬态(任何其他值)。您可能还记得第二个教程中的这个属性。
2.content_type:用于描述编码的mime类型。例如,对于经常使用的JSON编码,最好将此属性设置为:application/ JSON。

3.reply_to:通常用于命名回调队列。
4.correlation_id:用于将RPC响应与请求关联起来。

相互关联id

在上述方法中,我们建议为每个RPC请求创建一个回调队列。这非常低效,但幸运的是,还有更好的方法——让我们为每个客户机创建一个回调队列。

这引发了一个新问题,在该队列中接收到响应之后,不清楚响应属于哪个请求。这时使用correlation_id属性。我们会为每个请求设置一个唯一的值。稍后,当我们在回调队列中接收到消息时,我们将查看此属性,并基于此,我们将能够将响应与请求匹配。如果我们看到一个未知的correlation_id值,我们可能会安全地丢弃消息——它不属于我们的请求。

您可能会问,为什么我们应该忽略回调队列中的未知消息,而不是错误导致失败?这是由于服务器端可能存在竞争条件。尽管不太可能,但RPC服务器可能会在向我们发送答案之后,但在向请求发送确认消息之前死亡。如果发生这种情况,重新启动的RPC服务器将再次处理请求。这就是为什么在客户机上我们必须优雅地处理重复的响应,而RPC应该是等幂的。

摘要

python3 RabbitMQ ( RPC!)_第1张图片

我们的RPC将这样工作:

当客户端启动时,它会创建一个匿名排他回调队列。
对于RPC请求,客户端发送具有两个属性的消息:reply_to(设置为回调队列)和correlation_id(设置为每个请求的唯一值)。
请求被发送到rpc_queue队列。

RPC工作人员(又名:服务器)正在等待该队列上的请求。当出现请求时,它会执行任务并使用reply_to字段中的队列将结果发送回客户机。

客户机等待回调队列上的数据。当消息出现时,它检查correlation_id属性。如果与请求中的值匹配,则返回对应用程序的响应。

Putting it all together

The code for 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')

def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

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)

channel.basic_qos(prefetch_count=1)
channel.basic_consume(on_request, queue='rpc_queue')

print(" [x] Awaiting RPC requests")
channel.start_consuming()

服务器代码相当简单:
像往常一样,我们首先建立连接并声明队列。
(11)我们声明斐波那契函数。它只假设有效的正整数输入。(不要期望这个方法对大的数字有效,它可能是最慢的递归实现)。
我们为RPC服务器的核心basic_consumption声明了一个回调。它在收到请求时执行。它完成工作并返回响应。
我们可能需要运行多个服务器进程。为了在多个服务器上平均分配负载,我们需要设置prefetch_count设置。

The code for rpc_client.py:

#!/usr/bin/env python
import pika
import uuid

class FibonacciRpcClient(object):
    def __init__(self):
        self.connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))

        self.channel = self.connection.channel()

        result = self.channel.queue_declare(exclusive=True)
        self.callback_queue = result.method.queue

        self.channel.basic_consume(self.on_response, no_ack=True,
                                   queue=self.callback_queue)

    def on_response(self, ch, method, props, body):
        if self.corr_id == props.correlation_id:
            self.response = body

    def call(self, n):
        self.response = None
        self.corr_id = str(uuid.uuid4())
        self.channel.basic_publish(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)

客户端代码稍微复杂一些:

(7)我们建立一个连接,通道和声明一个排他性的回复“回调”队列。
(16)我们订阅“回调”队列,以便接收RPC响应。
(18)在每个响应上执行的’on_response’回调做的是一项非常简单的工作,对于每个响应消息,它检查correlation_id是否是我们正在寻找的对象。如果是这样,它将保存self的响应。响应并打破消费循环。
接下来,我们定义我们的主调用方法——它执行实际的RPC请求。
(24)在这个方法中,首先我们生成一个唯一的correlation_id号并保存它—'on_response’回调函数将使用这个值来捕获适当的响应。
接下来,我们发布请求消息,具有两个属性:reply_to和correlation_id。
在这一点上,我们可以坐下来等待适当的反应。
(33)最后我们将响应返回给用户。

你可能感兴趣的:(RabbitMQ)