bigywater.tech
Spring · 139

Spring AMQP 的重试与死信

重试了几次、间隔多久?max-attempts 为什么不管用了?重试用完消息去哪了?重试会不会堵住其他消息?怎么做不阻塞的延迟重试?消息格式错了会不会死循环?这篇在 Spring AMQP 4.0.5 和真实 RabbitMQ 上逐项实测。

max-attempts 被忽略
Spring Boot 4 里设 max-attempts=1 或 2,监听方法照样被调用 4 次;要用 max-retries
7.0 秒
同步重试(1+2+4 秒)期间,后面那条正常消息 ok-2 被处理的时刻
0 次调用
消息不是合法 JSON 时,监听方法被调用的次数,消息直接进死信

监听重试

实测 · 本机执行输出
  监听方法被调用 4 次,时刻 [0.0, 0.5, 1.5, 3.6] 秒;重试用完之后队列状态 (0, 0)(ready, unacked);消息去哪了:没有死信配置,被丢弃

原地重试,间隔指数增长,用完默认丢弃。

max-retries

实测 · 本机执行输出
  max-attempts=1:被调用 4 次
  max-attempts=2:被调用 4 次
  max-retries=0:被调用 1 次
  max-retries=1:被调用 2 次
  max-retries=2:被调用 3 次

max-attempts 被忽略,总调用次数 = max-retries + 1。

死信队列

实测 · 本机执行输出
  重试用完之后:q.work=(0, 0),q.dlq=(1, 0)
  q.dlq 里的消息:{'body': 'poison-1', 'x-first-death-reason': 'rejected', 'x-first-death-queue': 'q.work', 'x-death 条目': [('q.work', 'rejected', 1L)]}

队列上配 DLX,消息带 x-death 头进入。

同步重试阻塞

实测 · 本机执行输出
  ok-2 在 [7.0] 秒才被处理(poison-1 占着唯一的消费者线程重试了约 7 秒)

占住消费者线程,堵住后面的消息。

等待队列

实测 · 本机执行输出
  0.0 秒  收到 poison-1
  0.0 秒  收到 ok-2
  0.0 秒  完成 ok-2
  0.7 秒  收到 poison-1(x-death 累计 2 次)
  1.4 秒  收到 poison-1(x-death 累计 4 次)
  1.4 秒  已重试 3 次,转存到 q.parking 并确认
  最终队列状态:{'q.wait': (0, 0), 'q.work': (0, 0), 'q.dlq': (0, 0), 'q.parking': (1, 0)}

TTL 队列加死信,不阻塞;x-death 每轮加 2。

转换失败

实测 · 本机执行输出
  监听方法被调用 0 次;3 秒后 q.work=(0, 1),q.dlq=(1, 0);再过 3 秒 q.work=(0, 0),q.dlq=(1, 0)
  q.dlq 里的消息:{'body': 'this is not json', 'x-first-death-reason': 'rejected', 'x-first-death-queue': 'q.work', 'x-death 条目': [('q.work', 'rejected', 1L)]}

致命异常,不重试直接进死信。

分步演示

AMQP 重试与死信
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。RabbitMQ 是自己起的独立实例;每个场景之前删除并重新声明队列;队列状态用 rabbitmqctl list_queues 读取;时间是相对发布消息那一刻的秒数,有 0.1 秒左右抖动
    • 没有验证:有状态重试(stateless=false)与 RepublishMessageRecoverer;死信消息的重新投递(shovel / 手工);延迟消息插件(rabbitmq_delayed_message_exchange);重试时事务与幂等;Quorum 队列自带的 delivery-limit 与 x-delivery-count;Streams;单个异常类型的重试白名单;max-attempts 被忽略的原因(只看到现象与配置元数据里的弃用提示)
    京ICP备2026067502号-1京公网安备11010502063487号