(使用 pika 0.9.8 Python客户端)
在前一篇教程中,我们创建了一个工作队列。一个工作队列背后的假设是,每个任务被精确地发送给一个工作者进程。在这个部分,我们将做一些完全不同的事情 -- 我们将把一条消息发送给多个消费者。这种模式称为"发布/订阅"。
要描述这种模式,我们将构建一个简单的logging系统。它将由两个程序组成 -- 地一个将发射log消息,而第二个将接收并打印它们。
在我们的logging系统中,接收者程序的每一个运行副本都将获得消息。通过那种方式,我们就可以执行一个接收者来把logs写入磁盘;同时我们可以执行另一个接收者来在屏幕上查看logs。
若要实现这些,则发布的消息必须被广播给所有的接收者。
Exchanges
在这份教程的前一部分,我们向/从一个队列发送/接收消息。现在是时候介绍一下Rabbit中完整的消息模型了。
让我们快速地回顾一下前面的教程中都讲了些什么:
一个生产者是发送消息的用户应用程序。
一个队列是存储消息的缓冲区。
一个消费者是接收消息的用户应用程序。
RabbitMQ中的消息模型的核心概念是,生产者从不直接给一个队列发送任何任何消息。实际上,通常情况下,生产者甚至不知道一个消息是否将被发送给任何的队列。
相反,生产者只能把消息发送给一个exchange。一个exchange是一个非常简单的东西。它一边从生产者接收消息,一边将消息推给队列。exchange必须精确地知道如何处理它所接收的一条消息。它是否应该被附加到一个特定的队列?它是否应该被附加到多个队列?或者应该丢弃它。决定的规则则由exchange type来定义。
有好几个exchange types可用:direct, topic, headers和fanout。我们会将注意力放在最后一个 -- fanout。让我们创建一个那种类型的exchange,并把它称为logs:
channel.exchange_declare(exchange='logs', type='fanout')
fanout exchange非常简单。正如你可能从它的名字所猜到的那样,它只是把它接收到的所有消息广播给它所知道的所有的队列。那正是我们需要我们的logger做到的。
要列出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。这些是默认创建的,但此时你几乎不可能需要使用它们。
在这份教程的前一部分,我们还不了解任何关于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为空的情形。另外两种组合的含义?
你可能记得我们前面使用了指定了名字的队列(还记得hello和task_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将会把消息附接到我们的队列。
你可以使用rabbitmqctl list_bindings列出bindings。
发送消息的生产者程序,看起来与前面教程里的没有太大的区别。最重要的改变是,我们现在想要向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。
原文链接。