死信队列(DLQ)设计与处置
死信队列(Dead Letter Queue,DLQ)用于承接消费失败且不再自动重试的消息,是故障隔离与人工介入的缓冲通道,而非长期归档存储。
核心定位
DLQ 的本质是临时流转通道,不是静态数据库:
| 维度 | 静态存储(如 MySQL 归档表) | DLQ(死信队列) |
|---|---|---|
| 数据状态 | 写入后基本不变,以查询为主 | 进入后会被重放或人工处理后移出 |
| 生命周期 | 长期保留 | 通常仅保留数天,可配置 TTL 自动清理 |
| 访问方式 | SQL 查询(SELECT ... WHERE) | 线性消费(拉取 + 提交 offset) |
| 设计目标 | 持久存储与审计 | 暂存异常消息,等待修复后重试 |
消息进入 DLQ 后的预期归宿只有两类:修复后重放回业务 Topic,或被运维/监控程序消费并归档分析。DLQ 不应作为消息的最终存储层。
Kafka 中的实现形态
在 Kafka 里,DLQ 通常是一个独立 Topic(命名如 xxx-dlq、xxx-retry),与普通 Topic 无本质区别,但在运维与使用上有三点约束:
-
数据量应极小
DLQ 流量暴涨通常意味着业务或依赖大面积故障,需要告警。与归档表不同,DLQ 一般只保留短期数据。 -
消费模式是 POP,而非 SELECT
处理程序通过 Consumer 逐条拉取,处理成功后提交 offset,而不是按业务键做随机查询。 -
必须配套重放(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 解决的是「消息处理失败不拖垮主链路」(收进来处理不了)。
生产环境处置流程
- 告警:监控 DLQ Topic 的写入速率、Lag、消息年龄(
timestamp与当前时间差)。 - 定位:消费 DLQ 样本,结合错误码、堆栈、消息体字段判断是代码 Bug、依赖故障还是脏数据。
- 修复:发布热修、恢复下游、或批量订正数据。
- 重放:将消息重新投递到原 Topic;重放前确认消费者已具备幂等能力,避免重复处理造成副作用。
- 清理:重放成功并提交 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 中保留 originalTopic、failureReason、firstFailedAt 等元数据,便于审计与排障。
配置建议
| 项 | 建议 |
|---|---|
| 保留时间 | retention.ms 设为 3~7 天,避免 DLQ 无限膨胀 |
| 分区数 | 通常 1~3 即可,DLQ 不是吞吐瓶颈 |
| 告警阈值 | 任意 5 分钟内写入 > 0 或 Lag > 100 即告警 |
| 重放幂等 | 业务层必须支持 At Least Once,重放前确认去重策略 |
一句话总结
DLQ 是 Kafka 中的故障隔离舱:消费失败的消息暂存于独立 Topic,通过监控、人工介入与重放机制恢复处理,而非替代数据库做长期存储。与 Outbox(保证发送)互补,共同构成消息可靠性的完整闭环。