连载中 11/20

消费失败与重试:重试队列与死信队列

2026-08-04 · 2459 阅读 · 0 评论 · 0 赞

先把失败分好类

消费失败不是一个东西,硬套同一种处理方式是灾难的开始。先分类:

  • 瞬时失败:数据库锁冲突、下游接口抖动、网络超时——等一会儿再试,大概率能成,重试有意义;
  • 永久失败:消息体缺字段、业务代码有 bug、订单压根不存在——试一万次也是白搭,重试无意义。

理想世界应该按失败类型路由,现实里 MQ 分不出来。于是工业界的共识做法是:有限次自动重试,重试耗尽转死信,死信靠人工

RocketMQ 的重试机制拆解

消费返回 RECONSUME_LATER 或抛异常,RocketMQ 不会原地重试,而是把消息投进这个消费者组专属的重试队列(%RETRY%组名),按延迟等级排队后重新投递:

  • 重投延迟逐次拉长:10s、30s、1m、2m、3m……一路递增到 2h——这就是退避(backoff),给故障的下游留恢复时间,避免一秒钟轰炸十六次;
  • 默认最多重试 16 次,全程约 2 小时;
  • 16 次全失败,消息进入死信队列(%DLQ%组名),退出自动重投的循环。

注意重试的另一面:第 7 篇说过,重试就是重复投递,幂等是重试机制成立的前提,没有幂等的重试是给事故加滤镜。

Kafka 的空白区

Kafka 没有内置的重试队列和死信,失败消息拉不拉、重不重、丢不丢,全看消费代码怎么写。社区的标准做法是自建重试 Topic:失败后按延迟档位发到 retry-5s、retry-30s、retry-1m 等主题,消费耗尽后进 dead-letter-topic。Spring Kafka 的 @RetryableTopic 把这套「非阻塞重试」封装成了注解,值得直接用。用 Kafka 做业务消息的团队,这块基建要自己补——这也是第 3 篇说「业务消息优先 RocketMQ」的另一个理由。

死信队列不是垃圾场,是待办箱

最怕的死信姿势是「从不看它」。死信里躺着的每一封都是「系统承认处理不了」的欠条,必须有人签收:

  • 监控告警:死信堆积量大于 0 就告警,这是死信管理的第一行代码;
  • 排查归类:看消息体、看异常栈,分清是 bug、脏数据还是外部依赖故障;
  • 修复后重放:bug 修了、数据补齐了,把死信消息重新投回原 Topic 重放。重放前确认消费逻辑已带幂等,重放等于一次批量重复投递;
  • 兜底登记:确实无法恢复的(业务已回滚等),人工登记后放行,让账面对得平。

毒消息自救 SOP

顺序消费章节提过毒消息卡队列的雷,这里给一套完整 SOP:

阶段动作
发现同一 msgKey 反复失败、死信告警触发、队列消费位点长时间不动
隔离临时跳过(记录 msgKey 与位点),先恢复队列水位,避免连坐
修复本地复现:补数据、修 bug、修依赖
重放控制台或脚本定向重发,监控消费结果确认闭环

重试设计清单

  • 重试次数和退避间隔按下游恢复速度定,业务默认(16 次,2 小时)通常够用;
  • 重试消息的异常和上下文要打全日志,msgKey 贯穿始终;
  • 死信告警是硬性要求,处理时效写进值班手册;
  • 重放工具提前备好,别等出事现场写;
  • 重试与幂等成对出现,评审时一起过。

小结

消费失败的治理公式:瞬时失败靠退避重试,永久失败靠死信人工,全程靠幂等保平安。重试队列是缓冲带,死信队列是待办箱——只要告警响得够及时,两者都不可怕。

单条消息的毛病说完了,下一篇说成片的问题:消息积压。十万条消息堆在队列里,消费者扁了,这时候先做什么、后做什么、哪些动作是饮鸩止渴?

503

10 年全栈工程师 · 503咖啡馆主理人

#消息队列#消费重试#死信队列#毒消息#退避

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 5 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞