消费端的确认与拒绝
为了保证消息从队列可靠地达到消费者, RabbitMQ 提供了消息确认机制(message acknowledgement) 。

消费者在订阅队列时,可以指定 autoAck 参数

  • 当 autoAck 等于 false 时, RabbitMQ 会等待消费者显式地回复确认信号后才从内存(或者磁盘)中移去消息(实质上 是先打上删除标记,之后再删除) 。
  • 当 autoAck 等于 true 时, RabbitMQ 会自动把发送出去的 消息置为确认,然后从内存(或者磁盘)中删除,而不管消费者是否真正地消费到了这些消息 。

当 autoAck 参数置为 false ,对于 RabbitMQ 服务端而言 ,队列中的消息分成了两个部分 : 一部分是等待投递给消费者的消息:一部分是己经投递给消费者,但是还没有收到消费者确认 信号的消息。 如果 RabbitMQ 一直没有收到消费者的确认信号,并且消费此消息的消费者己经 断开连接,则 RabbitMQ 会安排该消息重新进入队列,等待投递给下一个消费者,当然也有可 能还是原来的那个消费者。
RabbitMQ 不会为未确认的消息设置过期时间,它判断此消息是否需要重新投递给消费者的 唯一依据是消费该消息的消费者连接是否己经断开,这么设计的原因是 RabbitMQ 允许消费者 消费一条消息的时间可以很久很久。

# 定义回调函数
# 取到消息后执行的回调函数
def callback(ch, method, properties, body):
loads_body = pickle.loads(body)
print(loads_body)
# 通知rabbitmq server,已经对消息处理完毕,可以释放掉保存的这个消息资源
# delivery_tag可以看作消息的编号 它是一个64位的长整型值,最大值是9223372036854775807
ch.basic_ack(delivery_tag=method.delivery_tag)
明确拒绝当前的消息而不是确认

消费者客户端可以调用与其对 应的 channel.basicReject 方法来告诉 RabbitMQ 拒绝这个消息

在Python中代码:

# 定义回调函数
# 取到消息后执行的回调函数
def callback(ch, method, properties, body):
loads_body = pickle.loads(body)
print(loads_body)
# 这里就是拒绝代码
# deliveryTag 可以看作消息的编号
# 如果 requeue 参数设置为 true ,则 RabbitMQ 会重新将这条消息存入 队列,以便可以发送给下一个订阅的消费者;
# 如果 requeue 参数设置为 false ,则 RabbitMQ 立即会把消息从队列中移除,而不会把它发送给新的消费者。
ch.basic_reject(delivery_tag=method.delivery_tag, requeue=True)

# Basic.Reject 命令一次只能拒绝一条消息 ,如果想要批 量拒绝消息 ,则可以使用 Basic.Nack 这个命令
# 此方法允许客户端拒绝一个或多个传入消息。它可用于中断和取消大量传入消息,或将无法处理的消息返回到其原始队列
ch.basic_nack(delivery_tag=None, multiple=True, requeue=True)
#第一个参数deliveryTag:发布的每一条消息都会获得一个唯一的deliveryTag,deliveryTag在channel范围内是唯一的
#第二个参数multiple:批量确认标志。如果值为true,包含本条消息在内的、所有比该消息deliveryTag值小的 消息都被拒绝了(除了已经被 ack 的以外);如果值为false,只拒绝三本条消息
#第三个参数requeue:表示如何处理这条消息,如果值为true,则重新放入RabbitMQ的发送队列,如果值为false,则通知RabbitMQ销毁这条消息。

注意要点:

将 channel.basicReject 或者 channel.basicNack 中的 requeue 设直为 false ,可以启用"死信队列"的功能。死信队列可以通过检测被拒绝或者未送达的消息来追踪问题

对于 requeue , 应的客户端方法为:AMQP 中还有一个命令 Basic.Recover 具备可重入队列的特性 。
其对(1) Basic.RecoverOk basicRecover() throws IOException;
(2) Basic.RecoverOk basicRecover(boolean requeue) throws IOException;
这个 channel.basicRecover 方法用来请求 RabbitMQ 重新发送还未被确认的消息 。 如 果 requeue 参数设置为 true ,则未被确认的消息会被重新加入到队列中,这样对于同一条消息 来说,可能会被分配给与之前不同的消费者。如果 requeue 参数设置为 false ,那么同 一条消 息会被分配给与之前相同的消费者。默认情况下,如果不设置 requeue 这个参数,相当于 channel.basicRecover(true) ,即 requeue 默认为 trueo

死信队列

DLX ,全称为 Dead-Letter-Exchange ,可以称之为死信交换器,也有人称之为死信邮箱。当消息在一个队列中变成死信 (dead message) 之后,它能被重新被发送到另一个交换器中,这个交换器就是 DLX ,绑定 DLX 的队列就称之为死信队列。

消息变成死信一般是由于以下几种情况:

  • 消息被拒绝 (Basic.Reject/Basic.Nack) ,井且设置 requeue 参数为 false;
  • 消息TTL过期;
  • 队列达到最大长度。

DLX 也是一个正常的交换器,和一般的交换器没有区别,它能在任何的队列上被指定 , 实 际上就是设置某个队列的属性。当这个队列中存在死信时 , RabbitMQ 就会自动地将这个消息重 新发布到设置的 DLX 上去 ,进而被路由到另一个队列,即死信队列

对于 RabbitMQ 来说, DLX 是一个非常有用的特性 。 它可以处理异常情况下,消息不能够 被消费者正确消费(消费者调用了 Basic.Nack 或者 Basic.Reject) 而被置入死信队列中 的情况,后续分析程序可以通过消费这个死信队列中的内容来分析当时所遇到的异常情况,进 而可以改善和优化系统。

死信的处理方式
  1. 丢弃,如果不是很重要,可以选择丢弃
  2. 记录死信入库,然后做后续的业务分析或处理
  3. 通过死信队列,由负责监听死信的应用程序进行处理
过期时间 (TTL)

TTL, Time to Live 的简称,即过期时间 。 RabbitMQ 可以对消息和队列设置 TTL。

设置消息的 TTL

目前有两种方法可以设置消息的 TTL。

  1. 第一种方法是通过队列属性设置,队列中所有消息 都有相同的过期时间。
  2. 第二种方法是对消息本身进行单独设置,每条消息的 TTL 可以不同。如果两种方法一起使用,则消息的 TTL 以两者之间较小的那个数值为准。

消息在队列中的生存时间一旦超过设置 的 TTL 值时,就会变成"死信" (Dead Message) ,消费者将无法再收到该消息

对于第一种设置队列 TTL 属性的方法,一旦消息过期,就会从队列中抹去,而在第二种方 法中,即使消息过期,也不会马上从队列中抹去,因为每条消息是否过期是在即将投递到消费者之前判定的。

为什么这两种方法处理的方式不一样?

因为第一种方法里,队列中己过期的消息肯定在队 列头部, RabbitMQ 只要定期从队头开始扫描是否有过期的消息即可。而第二种方法里,每条消 息的过期时间不同,如果要删除所有过期消息势必要扫描整个队列,所以不如等到此消息即将 被消费时再判定是否过期 , 如果过期再进行删除即可。

设置队列的 TTL

通过 channel . queueDeclare 方法中的 x- expires 参数可以控制队列被自动删除前处 于未使用状态的时间。未使用的意思是队列上没有任何的消费者,队列也没有被重新声明,并 且在过期时间段 内也未调用过 Basic . Get 命令。

设置队列里的 TTL 可以应用于类似 RPC 方式的回复队列,在 RPC 中,许多队列会被创建 出来,但是却是未被使用的。

RabbitMQ 会确保在过期时间到达后将队列删除,但是不保障删除的动作有多及时 。在 RabbitMQ 重启后 , 持久化的队列的过期时间会被重新计算。

延迟队列

延迟队列存储的对象是对应的延迟消息,所谓"延迟消息"是指当消息被发送以后,并不想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费

延迟队列的使用场景有很多,比如:

  • 在订单系统中, 一个用户下单之后通常有 30 分钟的时间进行支付,如果 30 分钟之内 没有支付成功,那么这个订单将进行异常处理,这时就可以使用延迟队列来处理这些订单了。
  • 用户希望通过手机远程遥控家里的智能设备在指定的时间进行工作。这时候就可以将 用户指令发送到延迟队列,当指令设定的时间到了再将指令推送到智能设备 。

在 AMQP 协议中,或者 RabbitMQ 本身没有直接支持延迟队列的功能,但是可以通过前面 所介绍的 DLX 和 TTL 模拟出延迟队列的功能。