RabbitMQ教程——发布/订阅

发布/订阅

(使用 pika 0.9.8 Python客户端)

前一篇教程中,我们创建了一个工作队列。一个工作队列背后的假设是,每个任务被精确地发送给一个工作者进程。在这个部分,我们将做一些完全不同的事情 -- 我们将把一条消息发送给多个消费者。这种模式称为"发布/订阅"。

要描述这种模式,我们将构建一个简单的logging系统。它将由两个程序组成 -- 地一个将发射log消息,而第二个将接收并打印它们。

在我们的logging系统中,接收者程序的每一个运行副本都将获得消息。通过那种方式,我们就可以执行一个接收者来把logs写入磁盘;同时我们可以执行另一个接收者来在屏幕上查看logs。

若要实现这些,则发布的消息必须被广播给所有的接收者。

Exchanges

在这份教程的前一部分,我们向/从一个队列发送/接收消息。现在是时候介绍一下Rabbit中完整的消息模型了。

让我们快速地回顾一下前面的教程中都讲了些什么:

  • 一个生产者是发送消息的用户应用程序。

  • 一个队列是存储消息的缓冲区。

  • 一个消费者是接收消息的用户应用程序。

RabbitMQ中的消息模型的核心概念是,生产者从不直接给一个队列发送任何任何消息。实际上,通常情况下,生产者甚至不知道一个消息是否将被发送给任何的队列。

相反,生产者只能把消息发送给一个exchange。一个exchange是一个非常简单的东西。它一边从生产者接收消息,一边将消息推给队列。exchange必须精确地知道如何处理它所接收的一条消息。它是否应该被附加到一个特定的队列?它是否应该被附加到多个队列?或者应该丢弃它。决定的规则则由exchange type来定义。

RabbitMQ教程——发布/订阅_第1张图片

有好几个exchange types可用:direct, topic, headers和fanout。我们会将注意力放在最后一个 -- fanout。让我们创建一个那种类型的exchange,并把它称为logs:

channel.exchange_declare(exchange='logs',
                         type='fanout')

fanout exchange非常简单。正如你可能从它的名字所猜到的那样,它只是把它接收到的所有消息广播给它所知道的所有的队列。那正是我们需要我们的logger做到的。

列出exchanges

要列出server上的exchanges,你可以运行有用的rabbitmqctl

$ sudo rabbitmqctl list_exchanges
Listing exchanges ...
logs      fanout
amq.direct      direct
amq.topic       topic
amq.fanout      fanout
amq.headers     headers
...done.

在这个列表中,有一些amq.* exchanges及默认的(无名的)exchange。这些是默认创建的,但此时你几乎不可能需要使用它们。

无名的exchange

在这份教程的前一部分,我们还不了解任何关于exchanges的东西,但依然能够向队列发送消息。那之所以能做到是因为我们使用了一个默认的exchange,我们用空字符串 ("")来标识它。

回忆一下前面我们是如何发布一条消息的:

channel.basic_publish(exchange='',
                      routing_key='hello',
                      body=message)

exchange参数是exchange的名字。空字符串表示默认的或无名的exchange:消息被路由到名字由routing_key描述的队列,如果它存在的话。

现在我们可以向我们的命名exchange发布消息了:

channel.basic_publish(exchange='logs',
                      routing_key='',
                      body=message)

basic_publish的调用中,exchange和routing_key的组合有4种,分别是,exchange为空,routing_key不为空;exchange为空,routing_key为空;exchange不为空,routing_key不为空;exchange不为空,routing_key为空。有两种组合的含义已经明了,即exchange为空routing_key不为空和exchange不为空routing_key为空的情形。另外两种组合的含义?

临时队列

你可能记得我们前面使用了指定了名字的队列(还记得hellotask_queue吗?)。能够命名一个队列对于我们非常重要 -- 我们需要向worker进程指出相同的队列。当你想要在生产者和消费者之间共享一个队列时,给队列一个名字很重要。

但那不是我们logger的情形。我们想要听到所有的log消息,而不仅仅是它们的一个子集。我们也仅仅对当前及之后的消息感兴趣,而不是过时的那些。要解决那些问题,我们需要做两件事。

首先,无论何时我们连接到了Rabbit,我们需要一个新的,空的队列。要做到这一点我们可以创建一个具有一个随机名字,或更好的情况 - 让服务器为我们选择一个随机的队列名字。我们可以通过不提供queue参数调用queue_declare来做到这一点:

result = channel.queue_declare()

此时result.method.queue包含一个随机的队列名。比如它看起来可能像amq.gen-JzTY20BRgKO-HjmUJj0wLg这样。

其次,一旦我们断开了消费者,则队列应该被删除掉。有一个exclusive flag可以做到这样点:

result = channel.queue_declare(exclusive=True)

绑定

我们已经创建了一个fanout exchange及一个队列。现在我们需要告诉exchange将消息发送到我们的队列中。exchange和一个队列那样的关系被称为绑定

channel.queue_bind(exchange='logs',
                   queue=result.method.queue)

从现在开始,logs exchange将会把消息附接到我们的队列。

列出bindings

你可以使用rabbitmqctl list_bindings列出bindings。

完整代码

RabbitMQ教程——发布/订阅_第2张图片

发送消息的生产者程序,看起来与前面教程里的没有太大的区别。最重要的改变是,我们现在想要向logs exchange发布消息,而不是无名的exchange。在发送时,我们需要提供一个routing_key,但它的值会由于fanout exchanges而被忽略掉。下面是emit_log.py脚本的代码:

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

connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='localhost'))
channel = connection.channel()

channel.exchange_declare(exchange='logs',
                         type='fanout')
                         
message = ' '.join(sys.argv[1:]) or "info: Hello World!"
channel.basic_publish(exchange='logs',
                      routing_key='',
                      body=message)
print " [x] Sent %r" % (message,)
connection.close()

(emit_log.py source)

如你所见,在建立了连接之后,我们声明了exchange。这一步是必须的,因为向一个不存在的exchange发布消息是被禁止的。

如果还没有队列绑定到exchange的话,则消息会丢失,但那对于我们来说算不上是什么问题;如果还没有消费者在监听的话,则我们能够安全的丢弃消息。

receive_logs.py的代码:

#!/usr/bin/env python
import pika

connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='localhost'))
channel = connection.channel()

channel.exchange_declare(exchange='logs',
                         type='fanout')

result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue

channel.queue_bind(exchange='logs',
                   queue=queue_name)

print ' [*] Waiting for logs. To exit press CTRL+C'

def callback(ch, method, properties, body):
    print " [x] %r" % (body,)

channel.basic_consume(callback,
                      queue=queue_name,
                      no_ack=True)

channel.start_consuming()
(receive_logs.py source)

我们完成了。如果你想要把logs保存到一个文件的话,则打开一个终端并输入:

$ python receive_logs.py > logs_from_rabbit.log

如果你想要在屏幕上看到logs的话,则派生一个新的终端并执行:

$ python receive_logs.py

当然要发射logs则键入:

$ python emit_log.py

使用rabbitmqctl list_bindings你可以验证,代码如我们所希望的那样实际创建了绑定和队列。有两个receive_logs.py程序运行时,你应该看到像下面这样的一些东西:

$ sudo rabbitmqctl list_bindings
Listing bindings ...
logs    exchange        amq.gen-JzTY20BRgKO-HjmUJj0wLg  queue           []
logs    exchange        amq.gen-vso0PVvyiRIL2WoV3i48Yg  queue           []
...done.

对于结果的解释很直接:来自于exchange logs的数据,流向两个由服务器分配名字的的队列。那正是我们想要的。

要了解如何监听消息的一个子集,让我们移向tutorial 4

Done。

原文链接

你可能感兴趣的:(RabbitMQ教程——发布/订阅)