RocketMQNotes

第 11 章:重试与死信

zjc 于 2026-01-11 发布

这是《RocketMQ 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 重试与死信是消费链路的最后防线。重试解决临时故障,死信收集无法自动恢复的消息。设计目标不是无限重试,而是在延迟、吞吐、数据安全和人工介入之间取得平衡。

11.1 消费重试流程

集群模式下,消费失败的消息进入重试队列:

Consumer
  -> consume fail
  -> RECONSUME_LATER
  -> retry topic: %RETRY%consumer-group
  -> delay by retry times
  -> retry consume
  -> final fail
  -> dead letter topic: %DLQ%consumer-group

重试次数由消费组配置控制。超过上限后进入死信队列,具体默认值和级别与客户端、Broker 版本相关,应以实际版本配置为准。

11.2 可重试与不可重试

异常 建议
数据库连接超时 重试
下游服务 503 重试
网络抖动 重试
参数格式错误 记录后确认
业务状态不允许 记录后确认
权限不足 人工处理
反序列化失败 死信治理

示例:

try {
    handle(msg);
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} catch (BusinessRejectException e) {
    deadLetterService.save(msg, e);
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} catch (TemporaryException e) {
    return ConsumeConcurrentlyStatus.RECONSUME_LATER;
} catch (Exception e) {
    deadLetterService.save(msg, e);
    return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}

不可修复异常直接进入死信或治理表,可以避免集群被脏消息拖垮。

11.3 重试延迟

重试延迟随次数增长,常见趋势:

10s -> 30s -> 1m -> 2m -> ... -> hours

设计原则:

  1. 首次重试不要过短,避免故障放大;
  2. 后续延迟指数增长;
  3. 设置最大重试次数;
  4. 核心业务保留人工处理入口;
  5. 对同一事件的重试做并发控制;
  6. 监控重试年龄。

11.4 死信队列

死信 Topic 命名通常为:

%DLQ%order-consumer-group

死信不是垃圾箱,而是待处理任务池。治理动作包括:

  1. 查看原消息、异常和堆栈;
  2. 修复代码或配置;
  3. 修复下游数据;
  4. 校验业务状态;
  5. 选择重放或放弃;
  6. 记录处理结论;
  7. 沉淀告警规则。

建议保存结构化治理记录:

{
  "messageId": "msg-id",
  "topic": "OrderTopic",
  "consumerGroup": "order-consumer-group",
  "retryTimes": 16,
  "firstFailureAt": "2026-08-25T10:00:00+08:00",
  "lastError": "InventoryService 503",
  "action": "REPLAY",
  "operator": "ops@example.com"
}

11.5 重放策略

重放前必须确认:

  1. 消费逻辑已修复;
  2. 消息体可解析;
  3. 下游容量足够;
  4. 幂等逻辑有效;
  5. 不会覆盖更新的业务状态;
  6. 顺序消息不会破坏时序;
  7. 批量重放有限速。

安全流程:

导出死信
  -> 人工或自动校验
  -> 灰度小批量重放
  -> 对比业务结果
  -> 分批处理剩余
  -> 记录处理结论

11.6 幂等与重试

重试天然带来重复执行:

consumer -> deduct inventory success
consumer -> commit offset fail
broker   -> redeliver message

处理方式:

@Transactional
public void handle(OrderEvent event) {
    if (processedEventRepository.insertIfAbsent(event.eventId()) == 0) {
        return;
    }
    inventoryService.reserve(event.orderNo());
}

同时为业务动作设计唯一键或状态机条件更新,避免“事件表唯一但业务重复执行”。

11.7 监控指标

consumer_retry_total
consumer_retry_delay_seconds
consumer_retry_active
dead_letter_total
dead_letter_oldest_age_seconds
dead_letter_unhandled_count
message_consume_age_seconds
business_reject_total

告警建议:

  1. 重试率突增;
  2. 单一错误类型集中出现;
  3. 死信持续增长;
  4. 死信未处理时间超过 SLA;
  5. 重试队列消息年龄过高;
  6. 消费成功率下降。

11.8 常见问题

问题 原因
消息一直重试 异常未分类、下游持续失败
死信快速增加 代码缺陷、消息格式变化、权限问题
重放重复扣款 幂等缺失
找不到重试 Topic 消费模式、版本或权限问题
顺序消息卡住 有序消费挂起未处理
死信处理后被覆盖 状态机缺少版本

本章小结

重试适合处理临时故障,死信负责承接无法自动恢复的消息。消费逻辑必须区分可重试与不可重试异常,设置有限重试和治理流程,并通过幂等、状态机、监控和审计保证重放安全。不要把死信当成终点,它是事件治理的起点。

思考题

  1. 为什么参数错误不适合无限重试?
  2. 死信队列和业务治理表如何配合?
  3. 重放前必须验证哪些条件?
  4. 重试为什么会放大下游故障?
  5. 死信未处理时间为什么比死信数量更适合告警?