常用参数
消息队列的参数
简单的消息队列内容回顾
-
简单模式下,RabbitMQ的消费者可以有多个消费者,同时监听一个队列,等待生产者发消息。
-
这种情况下,一个消费者从队列中取出消息,将自动反馈给队列,即自动确认;收到消费者的确认消息后,RabbitMQ将删除队列中的这条消息,并将下一条消息发给下一个消费者。即此时默认采用轮询分发(Round-robin dispatching)的方式。
这种简单的模式,是无法满足实际需求的,需要进一步解决以下三个问题。
- Q1 消费者从队列中取出数据,队列中的这个数据就没了,此时万一这个消费者死掉了,这个消息就丢失了。这类似于,任务就是给员工A做,A做不了死掉了,那任务就黄了,项目取消。
- Q2 如果RabbitMQ死掉了,重启RabbitMQ后,数据也就丢失了。
- Q3 因为采用轮询的方式分发消息,可能出现一个问题:消费者A接到消息后还没处理完毕,但是此时轮询发布消息的时候又轮到消费者A,此时RabbitMQ还是将这条消息发给A。即使此时消费者B在休息也没办法接收这个新消息,只有等到下一个属于B的消息才会发给B。这类似于任务热人选是提前内定好的。
上述三个问题的解决办法,在RabbitMQ中,可以通过以下三个参数设置:
- 消息确认(Message acknowledgment)
- 消息持久化(Message durability)
- 公平分发(Fair dispatch)
消息确认
为了解决Q1,RabbitMQ采用手动的方式反馈消息。
原理:
- 当消费者A收到消息处理完毕后,手动返回确认消息,RabbitMQ立即删除队列中的该条消息。
- 如果消费者A收到消息了,但是由于自己的BUG,导致无法处理消息任务,此时无法确认消息;
- RabbiTMQ收不到确认消息,则将消息分发给下一个消费者B,如果消费者B处理完消息并返回消息确认,RabbitMQ再将这条消息从队列中清除。
- 这样保证了任何一条消息都能被处理,不是因为消费者的原因而丢失。
RabbitMQ默认的消息确认是手动的,即channel.basic_consume()
中的参数auto_ack
默认是False
手动消息确认时,消费者需要在回调函数中,手动添加消息确认代码 ch.basic_ack(delivery_tag=method.delivery_tag)
import pika
# 第一步:连接
parameters = pika.ConnectionParameters(host='localhost')
connection = pika.BlockingConnection(parameters)
channel = connection.channel()
# 第二步:建队列
channel.queue_declare(queue='hello')
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
ch.basic_ack(delivery_tag=method.delivery_tag) # 需要手动设置消息确认
# 设置监听参数
channel.basic_consume(queue='hello', on_message_callback=callback) # auto_ack默认是False
# 第三步:开启监听任务
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
补充:
-
消息确认,牺牲执行效率,保证消息的安全
-
回调函数中,可能经常忘记添加手动确认回复信息的代码
basic_ack
。这是很浪费资源的。因为,当A成功处理了消息但是忘记了消息确认,此时这个消息又会分配给下一个消费者B。可以通过如下命令查看没有确认的消息。
sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged # linux
rabbitmqctl.bat list_queues name messages_ready messages_unacknowledged # windows
消息持久化
为了解决Q2,RabbitMQ提供了消息持久化的设置
消息持久化操作需要两点:队列持久化、消息持久化
-
队列持久化,建立队列是声明是持久化的队列
durable=True
channel.queue_declare(queue='new_queue_name', durable=True)
-
消息持久化,发布消息时设置消息的持久化属性
delivery_mode = 2
channel.basic_publish(exchange='', routing_key="new_queue_name", body=message, properties=pika.BasicProperties( delivery_mode = 2, # make message persistent ))
持久化的设置主要的生产者,消费者只需要配合声明-建立同样持久化的队列即可。
# 生产者.py
import pika
...
# 第二步,建队列
channel.queue_declare(queue='new_queue_name', durable=True) # 队列持久化
# 第三步,发消息
properties = pika.BasicProperties(delivery_mode=2) # 消息持久化
channel.basic_publish(exchange='',
routing_key='new_queue_name',
body='Hello World!',
properties=properties)
...
# 消费者.py
...
# 第二步,建队列
channel.queue_declare(queue='new_queue_name', durable=True)
...
补充:
- 这种持久化并不是完全的持久化,因为这种持久化数据时保存在缓存中的,不是保存在硬盘上的。
- 队列持久化时,不能将已经存在的非持久化队列设置为持久化队列。
- 另外,生产者和消费者都需要做队列持久化的工作。
公平分配
为了解决Q3,RabbitMQ提供了另一种消息分发模式Fair dispatch
所谓的公平分发,类似于“抢单”模式,即消息将分发给“空闲”的消费者。
实现分发的操作时给消费者添加设置 channel.basic_qos(prefetch_count=1)
原理:
-
轮询的方式下,RabbitMQ只是将进入队列的消息按照先后顺序分发给先后连接到这个队列上的消费者。每条消息分发给的消费者基本是固定的。
-
公平分发的方式下,消费者告诉RabbitMQ,一次只分发一个消息。也就是说当这个消费者回复的上一个消息确认后再给他分发新的消息。
-
需要注意,只能在手动确认消息的模式下才能使用公平分发。因为自动确认消息模式下,消费者接到任务就自动确认消息了,于是可以同时领多个消息。这就相当于,外卖小哥旧订单还没送出去,又接了新一个新订单。
-
本质上,公平分发还是基于队列的先进先出原则,优先将消息分配给“内定的外卖小哥”,只不过手动确认消息的模式下,不得不将消息交给下一个“空闲”的外卖小哥。
补充:prefetch_count
-
公平永远都是相对的。原因如下:
-
channel.basic_qos
中的prefetch_count
参数是表示消费者的处理消息的能力。即为每个consumer指定最大的unacked messages数目。简单来说就是用来指定一个consumer一次可以从Rabbit中获取多少条message并缓存在client中(RabbitMQ提供的各种语言的client library)。一旦缓冲区满了,Rabbit将会停止投递新的message到该consumer中直到它发出ack。参考博客
代码合在一起
生产者.py
import pika
# 第一步,连接rabbitmq
parameters = pika.ConnectionParameters(host='localhost')
connection = pika.BlockingConnection(parameters)
channel = connection.channel()
# 第二步,建队列
channel.queue_declare(queue='new_queue_name', durable=True) # 队列持久化
# 第三步,发消息
properties = pika.BasicProperties(delivery_mode=2) # 消息持久化
channel.basic_publish(exchange='',
routing_key='new_queue_name',
body='Hello World!',
properties=properties)
print(" [x] Sent 'Hello World!'")
connection.close()
消费者.py
import pika
# 第一步,连接rabbitmq
parameters = pika.ConnectionParameters(host='localhost')
connection = pika.BlockingConnection(parameters)
channel = connection.channel()
# 第二步,建队列
channel.queue_declare(queue='new_queue_name', durable=True) # 队列持久化
def callback(ch, method, properties, body):
time.sleep(20)
print(" [x] Received %r" % body)
ch.basic_ack(delivery_tag=method.delivery_tag) # 手动消息确认设置
# 设置监听参数
channel.basic_qos(prefetch_count=1) # 公平分发设置,一次只能接收一个消息
channel.basic_consume(queue='new_queue_name',
on_message_callback=callback) # 默认auto_ack=False
# 第三部,开启监听
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
补充
- 查看当前在线的消费者个数及信息
sudo rabbitmqctl list_connections