本文用于解答下述疑问:
消息在下面四个条件的处理方式:
- 两种模式
- 是否有异常、是否捕获异常
- 是否设置重试
requeue为true / false( +default-requeue-rejected的影响)
看似是 2 4 种方案,让人头疼,但其实没那么复杂。总结中也会给出简洁的答案。
正文
本文环境: springboot 2.1.9.RELEASE + amqp-client-5.4.3.jar
本文内容都是 direct相关的配置,这里的direct不是指直连模式的那个,而是指springbbot配置中rabbitMQ的属性。
即application.properties的配置
#注意用direct要声明
spring.rabbitmq.listener.type=direct
#是direct
spring.rabbitmq.listener.direct.xxxx
#而不是 spring.rabbitmq.listener.simple.xxxx
不了解两者区别的可以参考一下RabbitMQ传输原理、五种模式
本文所说的模式仅限于auto和manual两种,none不做讨论(毕竟就是"不管")
本文也不讨论这两种模式的优缺点,(auto时,源源不断地发送消息之类)
为简化讨论,"异常"只分两种: (这也是JVM处理的逻辑)
- 没有异常 和 有异常捕获了,都视为没有异常
- 有异常, 或者 捕获了异常再抛出、手动抛出,都视为有异常
(补充:全局异常处理器无法生效,只看该方法内有没有抛出)
首先前置知识:
-
RabbitMQ对异常是一无所知的,它只根据收到的
ack / nack / reject以及requeue来处理该消息 -
异常是在消费端内部处理的
-
无论哪种模式,无论哪种异常,只要设置了重试,抛出异常,消费端就会进行重试。
超出重试限制后,会根据当前的模式进行不同处理。
auto自动确认
-
消息成功被消费,没有抛出异常,则自动确认,回复ack。
不涉及
requeue,毕竟已经成功了。requeue是对被拒绝的消息生效。 -
当抛出
ImmediateAcknowledgeAmqpException异常,则视为成功消费,确认该消息。笔者看法:这个异常应该是设计来手动抛出,让MQ对该消息进行确认。
-
当抛出
AmqpRejectAndDontRequeueException异常的时候,则消息会被拒绝,且requeue = false(该异常会在重试超过限制后抛出) -
其他的异常,则消息会被拒绝,且
requeue = true
manual人工确认
无论有没有异常,标准只有是否主动调用basicAck()、basicNack()等方法,没有调用则一直阻塞
-
因此有异常时,(有重试就重试),抛出异常后,没有调用时,还是会一直阻塞。
即使是
auto模式的那两个特殊的异常,在manual中都是一样的,不会有特殊处理
default-requeue-rejected 属性
default-requeue-rejected属性的准确定义是:
【 (若有重试)重试次数超过限制后】,是否将被拒绝的消息重新入队。
注意:对象是被拒绝的消息。(这就是
requeue的作用对象)所以不要期望能通过该属性解决manual模式的异常的问题,如果你根本没响应消息,就不符合"被拒绝"的概念。
这里需要注意优先级的问题:
最高优先级:
AmqpRejectAndDontRequeueException处理的requeue = false(剩下那个特殊异常视为成功,与此处无关)basicNack() 的 boolean requeue参数
次优先级:
default-requeue-rejected
最低优先级:
auto模式中,处理其他异常时,拒绝消息,且requeue = true
这里有两个点:
AmqpRejectAndDontRequeueException的结果不会被该属性覆盖,所以我们可以确保:消息超出重试限制后,被拒绝,且requeue = false,成为死信。可以被死信队列所接收basicNack()的requeue参数是必须设置的
所以该属性的意义就在于覆盖掉最低优先级中,对其他异常的重入队true,防止死循环
(注:此时也是成为了死信的)
导致死循环的情况
首先需要提及一下:
无论哪种模式。requeue = true ,消息被拒绝,重回队列后,消息还是在队列头部。而不会到队列尾部。
而此处讨论导致死循环的,是指:消息本身有问题,且假设同一个消费者
(多个消费者可能不会死循环,但还是会消耗大量资源)
-
auto模式,没有设置重试,且
requeue = true(默认的)抛异常(指两个特殊异常外的) -> 消息被拒绝,重入队头 -> 再发送 -> 抛异常 …
因为如果设置了重试,会触发
AmqpRejectAndDontRequeueException异常,不赘述 -
maual模式,
Nack / reject时,requeue = true。
拒绝 -> 重入队 -> 再发送 -> 拒绝 …
对于死循环,有两种解决方法:
-
消费端中,直接重新发送该消息,此时会到队列尾部,然后删除该消息。
但缺点是,该消息如果本身有问题,也会无限重复,只是不是死循环,一样是浪费资源的。需要通过限制处理次数,记录下来,人工解决。
-
设置
requeue = false,配合死信队列记录。(
auto通过default-requeue-rejected覆盖,manual直接在basicNack中指定)
总结
-
两种模式:
manual简单暴力,只看有否调用api进行回应 -
是否有异常、是否捕获异常
- 对MQ,标准只有收到的
ack / nack及requeue属性 - 消费端识别异常,有重试则重试,无重试
- 对MQ,标准只有收到的
-
是否设置重试
重试只是消费端的一种处理方式,为了减少网络等影响。
重试最大的意义:
能够抛出特殊异常:
AmqpRejectAndDontRequeueException,auto模式下识别该异常,直接拒绝且requeue = false,让消息成为死信。 -
requeue为true / false( +default-requeue-rejected的影响)requeue只对被拒绝的消息有效default-requeue-rejected唯一意义在于 :设置为false,让
auto模式下,将非特殊异常而被拒绝的消息,覆盖为不重入队列,成为死信,防止死循环。
参考博文:
Rabbitmq和spring-rabbitmq中的DLX-拒绝消息的一些注意事项
本文完,有误欢迎指出。
转载请注明原文地址,https://blog.youkuaiyun.com/Unknownfuture/article/details/107950572

本文详细解析RabbitMQ消费者确认模式,包括auto自动确认和manual人工确认,以及default-requeue-rejected属性的影响。在异常处理上,auto模式下异常会导致消息被拒绝并重入队列,manual模式需要手动确认或拒绝消息。default-requeue-rejected属性主要用于防止死循环,结合死信队列使用能有效管理异常消息。
829





