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


    发布/订阅

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

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

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

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

    交易所

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

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

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

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

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

    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)
    • 1
    • 2
    • 3

    临时队列

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

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

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

    result = channel.queue_declare()
    • 1

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

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

    result = channel.queue_declare(exclusive=True)
    • 1

    绑定

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

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

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

    列出所有绑定

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

    整合

    这里写图片描述 
    用来发送日志消息的生产者程序,看起来和之前并没有多大不同。最大的变化是我们现在想要发送消息给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()
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    如果没有队列连接(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()
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

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

    python receive_logs.py > logs_from_rabbit.log
    • 1

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

    python receive_logs.py
    • 1

    下两图分别为在我的Ubuntu终端使用cat命令输出的存入到日志文件的日志和屏幕显示的日志 
    图1 
    1 
    图2 
    这里写图片描述 
    当然需要运行emit_log.py 
    这里写图片描述 
    使用 rabbitmqctl list_bindings 你可以确认代码创建了绑定和队列,就像我们想要的那样。在两个运行的情况下你应该会看到如下内容:

    sudo rabbitmqctl list_bindings
    # => Listing bindings ...
    # => logs    exchange        amq.gen-JzTY20BRgKO-HjmUJj0wLg  queue           []
    # => logs    exchange        amq.gen-vso0PVvyiRIL2WoV3i48Yg  queue           []
    # => ...done.
    • 1
    • 2
    • 3
    • 4
    • 5

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

     
  • 相关阅读:
    漫谈iOS程序的证书和签名机制
    (转) Xcode 7 Bitcode
    iOS: How To Make AutoLayout Work On A ScrollView
    Objective C运行时(runtime)
    如何让iOS 保持界面流畅?这些技巧你知道吗
    iPhone 6 屏幕揭秘
    用HTML和CSS实现WWDC 2015上的动画效果
    桌球歷史:削球、快攻、弧圈球
    [WPF 自定义控件]自定义控件库系列文章
    UWP 自定义控件:了解模板化控件 系列文章
  • 原文地址:https://www.cnblogs.com/ExMan/p/10283752.html
Copyright © 2020-2023  润新知