bigywater.tech
Kafka 内部原理 · 67

Dead Letter Queue:失败消息如何隔离

发5条订单,其中2条真实构造成非法数据,验证一个真实的consumer+DLQ实现如何把处理失败的消息隔离到单独的topic,同时保证主流程的offset照常推进不被卡住。

3条成功+2条DLQ
5条订单真实的处理结果分流
offset照常推进
失败消息真实不会卡住主流程消费

验证:故意在5条订单里埋两颗雷

命令 · python orders = [ {"id": 1, "amount": 100}, {"id": 2, "amount": -50}, # 真实非法:金额不能为负数 {"id": 3, "amount": 200}, {"id": "bad-json", "amount": "oops"}, # 真实非法:金额不是数字 {"id": 5, "amount": 300}, ] def process_order(order): amount = order["amount"] if not isinstance(amount, (int, float)): raise ValueError(f"amount不是数字: {amount!r}") if amount < 0: raise ValueError(f"金额不能为负数: {amount}") return amount * 1.1

验证:失败的消息真实被单独转发,主流程一条不卡

命令 · python try: result = process_order(order) except Exception as e: dlq_producer.produce(DLQ, value=json.dumps({ "original": order, "error": str(e), "failed_at_offset": m.offset() }).encode()) c.commit(m) # 真实无论成功失败,offset都照常提交,不卡在这条消息上
真实实测 · 本机执行输出
真实处理成功: [(1, 110.00000000000001), (3, 220.00000000000003), (5, 330.0)]
真实处理失败(已转入DLQ): [(2, '金额不能为负数: -50'), ('bad-json', "amount不是数字: 'oops'")]

真实DLQ topic里的内容(共2条):
  {'original': {'id': 2, 'amount': -50}, 'error': '金额不能为负数: -50', 'failed_at_offset': 1}
  {'original': {'id': 'bad-json', 'amount': 'oops'}, 'error': "amount不是数字: 'oops'", 'failed_at_offset': 3}
DLQ里保存的真实不只是原始消息本身,还带上了失败原因和它在原始topic里的offset——这些信息真实是后续排查和重放的关键:知道了"为什么失败",运维或者开发才能判断这是数据本身有问题(需要联系上游修正后重新处理),还是处理逻辑本身有bug(需要修代码后从DLQ里把消息重放回主流程)。更关键的是c.commit(m)这一句——不管这条消息处理成功还是失败,offset真实都会推进,处理失败的消息不会让consumer卡在原地重复重试同一条坏数据,这是DLQ模式真实要解决的核心问题:一条脏数据不该拖垮整条流水线。

分步演示:正常订单和问题订单真实的分流

每一步都是本文上面用真实处理逻辑跑出来的原始结果。

失败消息真实被隔离到DLQ,主流程一条不卡
这一步新验证的处理结果

    参考与说明

    • 数据来自本机3节点Kafka 4.3.1集群,topic orders-to-process(主流程)和orders-dlq(死信队列),一个真实的Python consumer实现处理+分流逻辑。
    • 没有验证:真实生产环境里DLQ消息的自动重放机制(修复问题后如何把DLQ里的消息重新送回主流程重试);DLQ消息本身也失败的情况(比如DLQ topic写入失败)该如何兜底。
    京ICP备2026067502号-1京公网安备11010502063487号