基于Python语言使用RabbitMQ消息队列(三)

发布/订阅

前面的教程中我们已经创建了一个工作队列。在一个工作队列背后的假设是每个任务恰好会传递给一个工人。在这一部分里我们会做一些完全不同的东西——我们会发送消息给多个消费者。这就是所谓的“发布/订阅”模式。

为了解释这种模式,我们将会构建一个简单的日志系统。它包含两个程序——第一个产生日志消息,第二个接收并把他们打印出来。

在我们的日志系统中,每一个接收程序的正在运行的拷贝都会获知消息,那样我们将能够运行一个接收者把日志指向磁盘;同时我们将能够运行另一个接收者在屏幕上查看日志。

实质上,被发布的消息将会广播给所有接收。

交易所

在前面的教程中我们向一个队列中发送和接收消息. 现在来介绍Rabbit中的完整的消息模型 .

我们快速回顾一下前面的教程:

  • 生产者是一个发送消息的用户应用。
  • 队列是一个存储消息的缓冲区。
  • 消费者是一个接收消息的用户应用。

RabbitMQ中消息模型的核心思想是,生产者从不直接发送任何消息给一个队列。实际上,生产者通常甚至一点都不知道一条消息会被发送给什么队列。
相反,生产者只能发送消息给一个交易所(exchange)。交易所是一个很简单的东西。它一面接收来自生产者的消息,一面把消息推送给队列。交易所必须准确知道如何对待它接收到的消息。它该被追加到一个特定队列中吗?还是应该把它追加到多个队列?又或者它该被忽略?这些规则都是由交易类型(exchange type)所定义的。
基于Python语言使用RabbitMQ消息队列(三)_第1张图片
有几种可用的交易类型: direct, topic, headers 和 fanout. 我们会关注最后一个——fanout. 我们来创建一个这种类型的交易所,命名为 logs:

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

fanout类型交易所非常简单。从名字中你可能已经猜到(其实英语非母语的也不大好猜到,fan(风扇)+out(外、出)的组合,我觉得在这里可以理解为扇出去、扬出去的意思),它只是广播它接到的所有消息给它知道的所有队列 ,这恰恰是我们的日志程序所需要的。

列出所有交际所

为了列出所有交易所,你可以运行Trabbitmqctl:

sudo rabbitmqctl list_exchanges 在这个列表中会有些像amq.* 的交易所和默认的(未命名的)交易所
这些是被默认创建的,但你目前不太可能需要用到它们。

默认交易所

在之前的教程中我们队交易所一无所知。但仍然能够给队列发送消息。这是因为我们使用了默认交易所,我们通过空字符串 (“”)来标识。

回顾一下我们之前如何发布一条消息:

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

exchange 参数就是交易所的名字。空字符串代表默认或者无名交易所:
消息路由到名字被routing_key所指定的队列,如果队列存在的话。

现在我们可以把消息发布到命名交易所了:

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

临时队列

你可能记得我们先前使用了有特定名字的队列 (记得 hello 和 task_queue?)。能给队列命名对我们来说很关键——我们需要把工人们指向同一个队列。当你想要在生产者和消费者中分享队列时,给队列一个名字就很重要。

但那不符合我们的日志程序需要。我们想获取所有日志消息,不只是他们的一个子集。我们也仅对当前活跃的消息感兴趣,而不是旧有的,解决这个问题我们需要做两件事。

第一,无论何时连接到Rabbit我们都需要一个新鲜的(fresh)空的(empty)队列 ,为实现这一点我们使用随机的名字创建一个队列,或者更好的——让服务器随机选一个队列名给我们。 我们不给queue_declare提供队列参数就可以做到:

result = channel.queue_declare()

这样的话, result.method.queue 就会包含一个任意的队列名,例如它可能看上去是这样的 : amq.gen-JzTY20BRgKO-HjmUJj0wLg.

第二, 一旦我们断开消费者链接,队列就该被删除掉。通过一个exclusive 标志实现:

result = channel.queue_declare(exclusive=True)

绑定

这里写图片描述
我们已经创建了一个fanout 交易所和一个队列。现在我们要通知交易所发送消息给我们的队列。交易所和一个队列之间的关系叫做绑定。

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

从现在开始logs交易所会追加消息给我们的队列。

列出所有绑定

你可以列出所有存在的绑定,使用…好吧,你已经猜到了:
rabbitmqctl list_bindings

整合

基于Python语言使用RabbitMQ消息队列(三)_第2张图片
用来发送日志消息的生产者程序,看起来和之前并没有多大不同。最大的变化是我们现在想要发送消息给logs交易所而不是无名交易所。当发送时我们需要提供一个routing_key,但对于fanout类型交易所来说它的值是被忽略的。 下面是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()

如你所见,建立完连接后我们生命了交易所。这一步是必须的,因为发布到一个不存在的交易所是被禁止的。

如果没有队列连接(bound)到交易所,消息就会丢失,但对我们来说没有关系;如果没有消费者监听我们可以安全地忽略掉这个消息。

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()

大功告成!如果你想把日志保存到一个文件中只需要打开控制台,键入:

python receive_logs.py > logs_from_rabbit.log

如果你想在屏幕查看日志, 打开一个新的控制台运行:

python receive_logs.py

下两图分别为在我的Ubuntu终端使用cat命令输出的存入到日志文件的日志和屏幕显示的日志
图1
1
图2
基于Python语言使用RabbitMQ消息队列(三)_第3张图片
当然需要运行emit_log.py
基于Python语言使用RabbitMQ消息队列(三)_第4张图片
使用 rabbitmqctl list_bindings 你可以确认代码创建了绑定和队列,就像我们想要的那样。在两个运行的情况下你应该会看到如下内容:

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

结果的解释很直白: 来自logs交易所的数据去往两个服务器指定了名字的队列 ,这正是我们所预想的。
若想知道如何监听消息子集,请前往下一节。

你可能感兴趣的:(Python,Web,开发)