为了账号安全,请及时绑定邮箱和手机立即绑定

RabbitMQ学习小结(三)—— Publish Subscribe[Python]

标签:
Python

1. 简介


前面的教程中,我们发送消息到队列并从中取出消息。现在是时候介绍RabbitMQ中完整的消息模型了。

让我们简单的概括一下之前的教程:

发布者(producer)是发布消息的应用程序。

队列(queue)用于消息存储的缓冲。

消费者(consumer)是接收消息的应用程序。


RabbitMQ核心概念,发布者是将消息直接发送给交换机,由交换机来决定消息是发送到哪个队列,或者是忽略消息
发布者(producer)只需要把消息发送给一个交换机(exchange)。交换机非常简单,它一边从发布者方接收消息,一边把消息推送到队列。交换机必须知道如何处理它接收到的消息,是应该推送到指定的队列还是是多个队列,或者是直接忽略消息。这些规则是通过交换机类型(exchange type)来定义的,可以参见下图:

而前两节我们的处理模式,都是没有指定交换机,看起来是直接指定到队列的。然而实际是,我们声明的是一个匿名交换器,exchange参数就是交换机的名称。空字符串代表默认或者匿名交换机:消息将会根据指定的routing_key分发到指定的队列。

2. 发布/订阅

上节我们的应用场景是所有worker处理事情的逻辑完全一致,队列中的消息交给任何一个worker处理都可以。而我们本节的应用场景是,实现一套简单的日志系统,两个消费者对消息的处理逻辑不一样,一个消费者负责将消息输出到屏幕,一个消费者负责写入到磁盘。因此,我们的消息不是发送给任意一个消费者,而是两个消费者都需要接收到所有的消息。本节我们就基于交换机来实现这个应用场景。

交换机(Exchanges)

RabbitMQ有四种内置交换机:

直连交换机——direct
主题交换机——topic
头交换机——headers
扇形交换机——fanout

也可以根据自身需求自定义交换机,本节不做深入探究。

我们本节使用的是扇形交换机,重点对他做些分析。

扇型交换机从名字上就能猜测出来,它把消息发送给它所知道的所有队列。这正是我们的日志系统所需要的。

交换器列表

可以通过rabbitmqctl list_exchanges查看队列信息


[root@iZ250x18mnzZ tRabbitMQ]# rabbitmqctl list_exchanges
Listing exchanges ...
amq.rabbitmq.trace      topic
amq.rabbitmq.log        topic
amq.match       headers
amq.headers     headers
amq.topic       topic
amq.direct      direct
amq.fanout      fanout
        direct
[root@iZ250x18mnzZ tRabbitMQ]#


例如amq.*都是系统内置的队列,最后一个没有名字的就是我们前两节所使用的空字符的交换机,这就是匿名交换机

匿名交换机

像前两节没有创建exchange也可以使用的情况,其实是系统默认创建了一个匿名交换机


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

其默认创建的交换机,其实是直连交换机,咱到下一节再对直连交换机进行分解。


可以像下面这样创建交换机。

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


临时队列

前两节我们在创建队列时,都是指定了队列名称(hello,task_queue),  队列名称显然是必须的,因为我们在worker中是需要指定我们处理哪个队列的内容。

但是我们本节的日志系统,显然用原来的方式是不可行的,因为我们是要处理队列中的所有消息,而不是使用队列中的某一部分消息,所以每个worker中的队列都应该是一个独立的、全新的、空队列。

我们可以手动创建一个随机的队列名,或者让服务器为我们选择一个随机的队列名(推荐)。我们只需要在调用queue_declare方法的时候,不提供queue参数就可以了


tmp_queue = channel.queue_declare()

队列已经创建出来了,显然我们需要队列的名字在消费时使用



<pre name="code" class="python"># 声明临时队列 , param exclusive 互斥
tmp_queue = channel.queue_declare(exclusive=True)
queue_name = tmp_queue.method.queue




当与消费者(consumer)断开连接的时候,这个队列应当被立即删除。exclusive标识符即可达到此目的。也就是上面那段代码中的参数。

绑定(bindings)

当队列与交换机都已经创建成功之后,如何将二者关联起来呢?

那我们就要通过绑定(bingdings)将二者关联起来,像下图一样


绑定列表

我们可以通过rabbitmqctl list_bindings查看列表


[root@iZ250x18mnzZ tRabbitMQ]# rabbitmqctl list_bindings
Listing bindings ...
        exchange        task_queue      queue   task_queue      []
[root@iZ250x18mnzZ tRabbitMQ]#


总结


经过上面提到的这么多关键词,我们队整个过程再重新总结一下

生产者



创建连接

创建交换机(扇形交换机,由交换机决定发送给哪个队列)

发送消息到指定交换机



消费者



创建连接

创建交换机(与生产者交换机对应)

创建临时队列(每个消费者都要创建的独立的队列,用于接收所有消息)

创建绑定(将交换机与临时队列绑定)

在指定交换机接收消息并处理

整合代码


emit_log.py


#!/usr/bin/env python# -*- coding: utf-8 -*-# @Date    : 2016-02-28 21:28:17# @Author  : mx (mx472756841@gmail.com)# @Link    : http://www.shujutiyu.com/# @Version : $Id$import osimport pikaimport sys

conn = Nonemessage = ' '.join(sys.argv[1:]) or "info: Hello World!"try:    # 获取连接
    conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))    # 获取通道
    channel = conn.channel()    # 声明交换机
    channel.exchange_declare(exchange='logs', 
                             type='fanout')    # 在RabbitMQ中发送消息,指定交换机(exchange)
    ret = channel.basic_publish(exchange='logs',
                                routing_key='',
                                body=message,)    print " [x] Sent '{0}'".format(message)    print retexcept Exception, e:    raise efinally:    if conn:
        conn.close()

recv_logs.py



#!/usr/bin/env python# -*- coding: utf-8 -*-# @Date    : 2016-02-29 16:30:21# @Author  : mx (mx472756841@gmail.com)# @Link    : http://www.shujutiyu.com/# @Version : $Id$import osimport pikaimport time

conn = Nonedef callback(ch, method, properties, body):
    """
        @ch: channel 通道,是由外部调用时上送
        out body
        读取队列内容做不同的操作
    """
    print " [x] Recived %r" % (body, )    print " [x] ch {0}".format(ch)    print " [x] method {0}".format(method)    print " [x] properties {0}".format(properties)    print " [x] Done %r" % (body, )try:    # get connection
    conn = pika.BlockingConnection(pika.ConnectionParameters(        'localhost')
    )    # get channel
    channel = conn.channel()    # 声明交换机
    channel.exchange_declare(exchange='logs', 
                             type='fanout')    # 声明临时队列 , param exclusive 互斥
    tmp_queue = channel.queue_declare(exclusive=True)
    queue_name = tmp_queue.method.queue    # 绑定交换机与队列
    channel.queue_bind( exchange='logs', queue=queue_name )    # 在队列中读取信息
    channel.basic_consume(callback, queue=queue_name, no_ack=True)    print ' [*] Waiting for messages. To exit press CTRL+C'

    channel.start_consuming()except Exception, e:    raise efinally:    if conn:
        conn.close()


注:



1.  如果交换机还没有绑定队列,即消费者还没有运行,而先运行了生产者,那交换机就自动把消息丢弃了

2.  上一节提到了队列和消息可以持久化,有兴趣的不妨试一下交换机的持久化

3. 参考资料


官网资料:http://www.rabbitmq.com/tutorials/tutorial-three-python.html

中文资料:http://rabbitmq-into-chinese.readthedocs.org/zh_CN/latest/tutorials_with_python/[3]Publish_Subscribe/


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消