Prompts-MCP 686 skills · ai / design / design-pattern / framework / fundamentals / habit / lang / tech-selection /mcp/sse
framework framework/kafka/error-handling.md high

kafka-error-handling

Kafka 消费错误处理 — DefaultErrorHandler 重试、死信队列 DLQ、阻塞 vs 非阻塞重试、毒丸消息隔离。Use when 消费抛异常要重试 / 配死信队列 / 隔离卡死分区的毒丸消息时。

错误处理死信队列重试dlq毒丸消息defaulterrorhandler
paths
  • *.java

Kafka · 消费错误处理

本条只管「消费抛异常后怎么重试、怎么进死信」。手动 ack 见 consumer-config.md;重试会放大重复消费,去重见 idempotent-consumer.md

规则

概念 说明
DefaultErrorHandler 替代旧 SeekToCurrentErrorHandler,配重试退避 + 最终 recoverer
DeadLetterPublishingRecoverer 重试耗尽后把消息投到 DLQ(默认 <topic>.DLT
阻塞重试 在当前 partition 原地重试,会卡住后续消息,适合短暂故障
非阻塞重试 @RetryableTopic 失败消息转入重试 topic 延迟再投,不阻塞主分区
毒丸消息 永远失败的消息(如反序列化失败);阻塞重试下会卡死整个分区,必须靠 DLQ 隔离

选型:故障是瞬时的(网络抖动)→ 阻塞重试几次即可;故障可能持久、不能卡后续消息 → 非阻塞重试 + DLQ。反序列化类毒丸用 ErrorHandlingDeserializer 直接转 DLQ,别进重试。

正例:重试退避 + DLQ

// ✅ 退避重试 3 次,仍失败投到 <topic>.DLT,不无限阻塞
@Bean
public DefaultErrorHandler errorHandler(KafkaTemplate<Object, Object> template) {
    var recoverer = new DeadLetterPublishingRecoverer(template);
    var backoff = new FixedBackOff(1000L, 3);   // 间隔 1s,最多重试 3 次
    return new DefaultErrorHandler(recoverer, backoff);
}
// ✅ 非阻塞重试:失败转入重试 topic,4 次后入 DLT,主分区不被卡
@RetryableTopic(attempts = "4", backoff = @Backoff(delay = 2000, multiplier = 2.0))
@KafkaListener(topics = "order-topic")
public void onMessage(String value) { handle(value); }

反例

// ❌ catch 后吞掉:失败消息既不重试也不进 DLQ,静默丢失
@KafkaListener(topics = "order-topic")
public void onMessage(String value, Acknowledgment ack) {
    try { handle(value); }
    catch (Exception e) { /* 吞掉 */ }
    ack.acknowledge();   // 照样提交 → 丢消息
}
// ❌ 无限阻塞重试且无 DLQ:一条毒丸消息卡死整个 partition,后面全堵住
new DefaultErrorHandler(new FixedBackOff(1000L, Long.MAX_VALUE));

自检

  • [ ] 配了 DefaultErrorHandler,重试次数有上限(不是无限阻塞)?
  • [ ] 重试耗尽后有 DeadLetterPublishingRecoverer 投 DLQ,没有静默丢消息?
  • [ ] 区分了阻塞(卡分区)与非阻塞重试,按故障是否持久选对?
  • [ ] 反序列化失败用 ErrorHandlingDeserializer 直接进 DLQ,没让毒丸卡死分区?
  • [ ] 重试可能放大重复消费,消费逻辑已幂等?

相关