必须抛出未捕获的RuntimeException才能触发重试,因为Spring AMQP仅在异常向上冒泡至框架层时自动执行basicNack(requeue=true);若try-catch吞掉异常,框架误判为处理成功而发送ack,导致重试机制失效。

必须让消费者抛出未捕获的异常,且 acknowledge-mode 设为 AUTO,否则重试不会触发。
为什么 throw new RuntimeException() 才有效,而 try-catch 吞掉异常就失效?
重试机制依赖 Spring AMQP(或 Laravel 的底层适配器)对异常的自动捕获。只要消息处理函数里没被 catch 住的异常向上冒泡,框架就会拒绝该消息并按配置重发。
-
acknowledge-mode: AUTO模式下,框架在 catch 到异常后自动执行basicNack(requeue=true) - 如果用
try-catch包住全部逻辑但没重新 throw,框架认为“处理成功”,直接发送ack - 手动调用
basicNack+requeue=true在MANUAL模式下也能重入队,但绕过了重试间隔、退避策略等配置
laravel-queue-rabbitmq 的 reroute_failed 和重试不是一回事
很多人混淆“失败重路由”和“消费重试”:reroute_failed 是把最终确认失败(比如达到最大重试次数后)的消息,转发到另一个 exchange(如 failed-exchange),进死信队列;它不参与中间的重试过程。
- 重试发生在原队列内,靠 RabbitMQ 的
requeue行为 + 框架的退避策略实现 -
reroute_failed: true只在所有重试都耗尽后才生效,是兜底动作 - 两者要配合使用:先靠重试解决临时故障,再靠 reroute 把顽固失败消息隔离出来人工干预
重试间隔和最大次数怎么配才不踩坑?
默认无间隔重试会打爆下游服务,必须显式配置退避策略。Laravel 自身不直接暴露 Spring 风格的 initial-interval,而是依赖 laravel-queue-rabbitmq 的 retry 配置项或底层 AMQP 客户端行为。
- 在
config/rabbitmq.php中设置:'retry_after' => 90(单位秒,表示消息被取走后多久未 ack 就视为失败) - 最大重试次数由队列的
x-delivery-limit参数控制,需在声明队列时传入:['x-delivery-limit' => 5] - 注意:RabbitMQ 的
x-delivery-limit是“最大投递次数”,实际重试次数 =x-delivery-limit - 1 - 若用 Horizon,还要检查
horizon.php中的retry_after和tries是否与 RabbitMQ 层一致,否则行为错位
真正容易被忽略的是:重试过程中消息的 delivery_tag 不变,但每重试一次,RabbitMQ 会在 header 里追加 x-death 记录;如果你在业务逻辑里依赖消息唯一性做幂等,得从 x-death 或自定义 header 里读取重试次数,而不是只看原始 payload。


















