Skip to main content

死信队列(DLQ)设计与处置

死信队列(Dead Letter Queue,DLQ)用于承接消费失败且不再自动重试的消息,是故障隔离与人工介入的缓冲通道,而非长期归档存储。

核心定位

DLQ 的本质是临时流转通道,不是静态数据库:

维度静态存储(如 MySQL 归档表)DLQ(死信队列)
数据状态写入后基本不变,以查询为主进入后会被重放或人工处理后移出
生命周期长期保留通常仅保留数天,可配置 TTL 自动清理
访问方式SQL 查询(SELECT ... WHERE线性消费(拉取 + 提交 offset)
设计目标持久存储与审计暂存异常消息,等待修复后重试

消息进入 DLQ 后的预期归宿只有两类:修复后重放回业务 Topic,或被运维/监控程序消费并归档分析。DLQ 不应作为消息的最终存储层。

Kafka 中的实现形态

在 Kafka 里,DLQ 通常是一个独立 Topic(命名如 xxx-dlqxxx-retry),与普通 Topic 无本质区别,但在运维与使用上有三点约束:

  1. 数据量应极小
    DLQ 流量暴涨通常意味着业务或依赖大面积故障,需要告警。与归档表不同,DLQ 一般只保留短期数据。

  2. 消费模式是 POP,而非 SELECT
    处理程序通过 Consumer 逐条拉取,处理成功后提交 offset,而不是按业务键做随机查询。

  3. 必须配套重放(Replay)机制
    标准处置流程:读取 DLQ 消息 → 修复根因(代码热修、依赖恢复、数据订正)→ 将消息体重新发送到原始业务 Topic,由正常消费链路再次处理。

消息何时进入 DLQ

常见路由条件(可在消费者侧或框架层实现):

  • 不可重试错误:消息格式非法、字段缺失、业务规则明确拒绝(如订单状态不允许扣库存)。
  • 重试耗尽:依赖服务超时、乐观锁冲突等可重试错误,在本地或 Retry Topic 重试 N 次仍失败后转发。
  • Poison Message:同一条消息反复失败,阻塞主队列时,应隔离到 DLQ 避免 Head-of-Line Blocking。

典型拓扑:

业务 Topic → Consumer(重试 N 次)→ 仍失败 → DLQ Topic

人工/工具消费 → 重放至业务 Topic

DLQ 与本地消息表(Outbox)的区别

二者常被混淆,但职责完全不同:

维度DLQ本地消息表(Outbox)
位置Kafka / RabbitMQ 等中间件业务库(MySQL / PostgreSQL)
作用存放处理失败的消息存放待发送的消息,保证与业务事务一致
生命周期短暂,修复后即消费删除持久,发送成功并确认后删除
查询方式线性消费SELECT ... WHERE status = 'PENDING'

Outbox 解决的是「消息必达」(发出去);DLQ 解决的是「消息处理失败不拖垮主链路」(收进来处理不了)。

生产环境处置流程

  1. 告警:监控 DLQ Topic 的写入速率、Lag、消息年龄(timestamp 与当前时间差)。
  2. 定位:消费 DLQ 样本,结合错误码、堆栈、消息体字段判断是代码 Bug、依赖故障还是脏数据。
  3. 修复:发布热修、恢复下游、或批量订正数据。
  4. 重放:将消息重新投递到原 Topic;重放前确认消费者已具备幂等能力,避免重复处理造成副作用。
  5. 清理:重放成功并提交 DLQ Consumer offset;对无法修复的消息做人工补偿或归档后丢弃。

重放接口示例

可通过独立的管理接口或定时任务实现单条/批量重放,核心逻辑如下:

// 伪代码:从 DLQ 拉取并重放
async retryFromDlq() {
const record = await this.dlqConsumer.pollOne();
if (!record) return;

const { originalTopic, payload, errorReason } = JSON.parse(record.value);
this.logger.warn({ errorReason, offset: record.offset });

await this.producer.send({
topic: originalTopic,
messages: [{ value: JSON.stringify(payload) }],
});

await this.dlqConsumer.commitSync();
}

重放时建议在消息头或 Value 中保留 originalTopicfailureReasonfirstFailedAt 等元数据,便于审计与排障。

配置建议

建议
保留时间retention.ms 设为 3~7 天,避免 DLQ 无限膨胀
分区数通常 1~3 即可,DLQ 不是吞吐瓶颈
告警阈值任意 5 分钟内写入 > 0 或 Lag > 100 即告警
重放幂等业务层必须支持 At Least Once,重放前确认去重策略

一句话总结

DLQ 是 Kafka 中的故障隔离舱:消费失败的消息暂存于独立 Topic,通过监控、人工介入与重放机制恢复处理,而非替代数据库做长期存储。与 Outbox(保证发送)互补,共同构成消息可靠性的完整闭环。