必须设default-requeue-rejected: false、retry.enabled: true且max-attempts≤5、队列声明时配置x-dead-letter-exchange,否则抛异常会导致消息无限重入队;manual模式下需显式basicnack(..., false)并依赖dlq兜底。

不是一直重新入队,但默认行为确实会反复投递,直到消费者 ACK 或连接断开;阻断的关键是关掉 default-requeue-rejected、配好重试上限、并启用死信队列。
为什么抛异常后消息看起来“永远在重试”?
Spring AMQP 默认使用 acknowledge-mode: auto,但这个“自动”只在无异常时发 ACK;一旦业务代码抛出未捕获异常,框架会触发 basic.reject(requeue=true)(即重入队)。此时消息不丢、不进死信、也不标记失败——它只是被插回队列头(经典队列)或尾部(Quorum Queue),等待下一次被捞起。监控上表现为 messages_ready 不降反抖,日志里同一 message_id 反复出现。
常见错误现象包括:
- 消费者 CPU 持续 80%+,但业务处理量为 0
-
rabbitmqctl list_queues name messages_ready messages_unacknowledged显示messages_unacknowledged长期为 0,而messages_ready卡住不动 - 堆栈里固定报
NullPointerException或NumberFormatException,行号不变
怎么让失败消息停止循环?
靠“不抛异常”或“try-catch 吞掉”不可靠——前者掩盖问题,后者等于主动丢消息。真正可控的阻断路径是三层配置组合:
- 关闭自动重入队:
default-requeue-rejected: false(这是最易忽略的开关,不加这句,后面所有重试配置都可能失效) - 开启有限重试:
retry.enabled: true+max-attempts: 5(建议不超过 5 次,避免压垮下游) - 绑定死信交换机:
x-dead-letter-exchange和x-dead-letter-routing-key必须在队列声明时设置,不能只在消费者配置里写
注意:initial-interval 和 multiplier 决定重试节奏,但若没配死信,第 6 次失败后消息就直接丢了,不会进 DLQ。
手动 ACK 模式下,basic.nack 的 requeue 参数怎么选?
如果你用的是 acknowledge-mode: manual,那每次都要显式调用 channel.basicNack(deliveryTag, false, requeue)。这里 requeue 是布尔值,不是开关选项:
-
requeue = true:消息回到队列(再次触发循环风险) -
requeue = false:消息被丢弃——除非你已配好死信,否则等于永久丢失
所以真实生产环境几乎不会设 requeue = true。更安全的做法是:捕获异常后,记录必要上下文(如 messageId、exception),然后调用 channel.basicNack(deliveryTag, false, false),靠死信机制兜底。
死信队列没生效?先查这三个地方
死信配置看似简单,但漏掉任意一项都会导致消息“消失”而非“转移”:
- 队列申明时是否带参数
x-dead-letter-exchange?仅在消费者 YAML 里写dead-letter-exchange不生效 - 死信交换机(DLX)是否存在?类型是否匹配(通常用
direct)? - DLX 是否绑定了死信队列(DLQ)?且绑定 key 是否与原队列设置的
x-dead-letter-routing-key一致?
验证命令:rabbitmqctl list_exchanges 看 DLX 是否在列表中;rabbitmqctl list_bindings 看 DLX 到 DLQ 的绑定是否存在。
最常被跳过的细节是:消息进入死信队列后,仍需一个独立消费者监听 DLQ —— 它不会自动告警或重推,不写这个消费者,就等于把问题从明处藏到了暗处。










