RabbitMQ学习小结(二)----工作队列

演示了多个接收端情况下,消息发送的工作方式。本小节将对工作队列(Work Queues)做一个了解。

一、简介

在Hello World中,学会从队列中发送和获取消息。实际应用中,并不是简单的接收和发送。当需要复杂的需求时,为了提升效率,

一个消费者处理不过来。 在这篇教程中,我们将创建一个工作队列(Work Queue),它会发送一些耗时的任务给多个工作者(Worker)。

RabbitMQ学习小结(二)----工作队列_第1张图片

工作队列(又称:任务队列——Task Queues)是为了避免等待一些占用大量资源、时间的操作。当我们把任务(Task)当作消息

送到队列中,一个运行在后台的工作者(worker)进程就会取出任务然后处理。当你运行多个工作者(workers),任务就会在

之间共享。

二、应用场景

在Hello World中只是单独的发送一个字符串,但是实际应用中可能是图片,可能是pdf处理等等,可能需要消耗的时间长短不一,

根据官网示例不做复杂操作,而是使用符号“.”来代表任务的难易程度,从而用time.sleep()来控制任务的时间长短。一个点

(.)将会耗时1秒钟。比如"Hello..."就会耗时3秒钟。

三、概念理解

1、循环调度

 使用工作队列(多个消费者接收处理队列中的消息)好处是能够并行处理队列消息(任务)。如果堆积很多任务或者消息,只需要添

加更多的工作者即可,扩展很简单。

我们先同时运行两个receive.py脚本,它们都会从队列中获取消息,到底是不是这样呢?我们看看。

你需要打开三个终端,两个用来运行receive.py脚本,这两个终端就是我们的两个消费者(consumers)—— C1 和 C2。

python receive.py
第三个新终端,我们发布新任务。可以发送消息给消费者(consumers):

python send.py First message.
python send.py Second message..
python send.py Third message...
可以看到我们给消费者发的消息内容。

RabbitMQ会按顺序得把消息发送给每个消费者(consumer)。平均每个消费者都会收到同等数量得消息。这种发送消息得方式叫

——轮询(round-robin)。

2、消息确认

 当运行的任务比较耗时,你也许想知道消费者(consumers)是否运行到一般会挂掉。当消息被RabbitMQ发送给消费者(consumers)

之后,马上就会在内存中移除。这种情况,你只要把一个工作者(worker)停止,正在处理的消息就会丢失。同时,所有发送到这

个工作者的还没有处理的消息都会丢失

处理方法:

为了防止消息丢失,RabbitMQ提供了消息响应(acknowledgments)。消费者会通过一个ack(响应),告诉RabbitMQ已经收到并处

理了某条消息,然后RabbitMQ就会释放并删除这条消息。

如果消费者(consumer)挂掉了,没有发送响应,RabbitMQ就会认为消息没有被完全处理,然后重新发送给其他消费者consumer。

这样,即使工作者(workers)偶尔的挂掉,也不会丢失消息。

消息响应默认是开启的。之前的例子中我们可以使用no_ack=True标识把它关闭。是时候移除这个标识了,当工作者(worker)完

了任务,就发送一个响应。

def callback(ch, method, properties, body):
    print " [x] Received %r" % (body,)
    time.sleep( body.count('.') )
    print " [x] Done"
    ch.basic_ack(delivery_tag = method.delivery_tag)  #消费者发送响应消息

channel.basic_consume(callback, queue='hello')

运行上面的代码,我们发现即使使用CTRL+C杀掉了一个工作者(worker)进程,消息也不会丢失。当工作者(worker)挂掉这后,

所有没有响应的消息都会重新发送。

忘记确认响应:忘了basic_ack,后果很严重。消息在你的程序退出之后就会重新发送,如果它不能够释放没响应的消息,RabbitMQ

就会占用越来越多的内存。


3、消息持久化

如果你没有特意告诉RabbitMQ服务器,那么在它退出或者崩溃的时候,将会丢失所有队列和消息。为了确保信息不会丢失,有两个

事情是需要注意的:我们必须把“队列”和“消息”设为持久化

(1)、队列持久化:为了不让队列消失,需要把队列声明为持久化(durable)

channel.queue_declare(queue='hello', durable=True)
因为我们定义了一个hello的非持久化队列。RabbitMQ不允许使用不同的参数重新定义一个队列,会返回一个错误。

使用不同的队列名:

channel.queue_declare(queue='task_queue', durable=True)
这个queue_declare必须在生产者(producer)和消费者(consumer)对应代码中修改。

这样可以确保在RabbitMq重启之后queue_declare队列不会丢失

(2)、消息持久化:将delivery_mode的属性设为2

channel.basic_publish(exchange='', routing_key="task_queue", body=message,
                      properties = pika.BasicProperties(delivery_mode = 2, # make message persistent))
注意:以上告诉了RabbitMq要把消息存到硬盘,但从 RabbitMq收到消息到保存之间还是有一个很小的间隔时间。因为RabbitMq并不

是所有的消息都使用fsync(2)——它有可能只是保存到缓存中,并不一定会写到硬盘中。并不能保证真正的持久化,但已经足够应

付我们的简单工作队列。如果你一定要保证持久化,你需要改写你的代码来支持事务(transaction)。

4、公平调度

RabbitMQ只管分发进入队列的消息,不会关心有多少消费者(consumer)没有作出响应。它盲目的把第n-th条消息发给第n-th个消

费者。没有按照我们的期望进行分发消息。

RabbitMQ学习小结(二)----工作队列_第2张图片

使用basic_qos方法,并设置prefetch_count=1。这样是告诉RabbitMQ,在同一时刻,不要发送超过1条消息给一个工作者worker,

直到它已经处理了上一条消息并且作出了响应。这样,RabbitMQ就会把消息分发给下一个空闲的工作者(worker)。

channel.basic_qos(prefetch_count=1)
关于队列大小:

如果所有的工作者都处理繁忙状态,你的队列就会被填满。你需要留意这个问题,要么添加更多的工作者(workers),要么使用其

他策略。

四、整合代码:

send.py

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

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

channel.queue_declare(queue='task_queue', durable=True)

message = ' '.join(sys.argv[1:]) or "Hello World!"
channel.basic_publish(exchange='',
                      routing_key='task_queue',
                      body=message,
                      properties=pika.BasicProperties(
                         delivery_mode = 2, # make message persistent  #消息持久化
                      ))
print " [x] Sent %r" % (message,)
connection.close()

receive.py

#!/usr/bin/python
import pika
import time

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

channel.queue_declare(queue='task_queue', durable=True) #队列持久化
print ' [*] Waiting for messages. To exit press CTRL+C'

def callback(ch, method, properties, body):
    print " [x] Received %r" % (body,)
    time.sleep( body.count('.') )
    print " [x] Done"
    ch.basic_ack(delivery_tag = method.delivery_tag) #响应

channel.basic_qos(prefetch_count=1)  #公平调度
channel.basic_consume(callback,
                      queue='task_queue')

channel.start_consuming()

使用 消息响应和prefetch_count你就可以搭建起一个工作队列了。这些持久化的选项使得在RabbitMQ重启之后仍然能够恢复。

程序结果自行运行。

参考资料:

http://rabbitmq.mr-ping.com/tutorials_with_python/[2]Work_Queues.html

你可能感兴趣的:(openstack组件研究)