Kafka 消费幂等:重复消息不可怕,重复副作用才可怕

📅 2026/7/6 11:17:13 👁️ 阅读次数 📝 编程学习
Kafka 消费幂等:重复消息不可怕,重复副作用才可怕

Kafka 消费幂等:重复消息不可怕,重复副作用才可怕

一、Kafka 里重复消费是必须接受的现实

Kafka 消费端常见目标是“不要重复消费”。但在真实系统里,重平衡、超时、提交 offset 失败、消费者重启都可能导致消息被再次处理。工程上更现实的目标不是完全消灭重复,而是让重复消息不会产生重复副作用。

比如订单状态更新、积分发放、短信通知、库存扣减,这些操作如果没有幂等保护,一条消息重复处理就可能造成业务事故。Kafka 本身提供的是消息传递能力,业务副作用要靠应用设计兜住。

二、消费流程要把处理和提交分开看

消息处理成功,不代表 offset 已成功提交。

flowchart TD A[拉取消息] --> B[解析校验] B --> C[幂等检查] C --> D[执行业务副作用] D --> E[记录处理结果] E --> F[提交 offset] F --> G[下一批消息]

如果 D 成功但 F 失败,消息可能再次被拉到。幂等检查必须覆盖这种情况。

三、业务幂等键要稳定

下面是一个简单结构。

public record ConsumeCommand( String messageId, String bizId, String eventType, String payloadHash ) {}

幂等键可以来自业务 ID、事件类型和版本号。不要只用 Kafka offset,因为重发、补偿或跨 topic 流转时 offset 不稳定。

四、幂等记录要和业务状态一致

常见做法是在数据库里建消费记录表,处理前检查,处理成功后记录。但要注意事务边界。业务更新成功、幂等记录失败,仍然会出问题。最好把业务状态变更和幂等记录放在同一个本地事务里。

对于外部副作用,比如发短信、调用支付、推送第三方接口,要尽量使用对方提供的幂等键。如果对方不支持幂等,就要把调用放在更谨慎的补偿流程里。

失败消息也要分类。临时失败可以重试,永久失败要进入死信队列或人工处理。无限重试会阻塞分区,影响后续正常消息。

最后,监控要看重复率和幂等命中率。重复消息本身不一定是事故,但重复率突然升高可能说明消费者不稳定、处理变慢或重平衡频繁。

幂等记录也要有生命周期。所有消息永久保存会让表越来越大,查询越来越慢。可以按业务保留周期归档或清理,但清理前要确认不会再发生补偿重放。保留时间太短,历史消息重放时又会失去幂等保护。

还要处理消息版本。业务事件升级后,同一个 bizId 可能出现不同版本的 payload。幂等键如果只看 bizId,就可能误把新版本事件当成重复消息跳过。事件类型和版本号应该进入幂等判断。

最后,消费者重平衡要减少不必要发生。处理时间过长、心跳配置不合理、批量太大,都会触发重复消费风险。幂等是兜底,不是放任消费端不稳定的理由。

对于高价值事件,还可以引入状态机。消息不是简单处理一次,而是从 received、processing、succeeded、failed、compensating 等状态流转。状态机比一个布尔幂等标记更适合复杂业务,也更方便人工介入。

顺序消息要额外小心。如果同一个业务对象的事件必须按顺序处理,重复消息和乱序消息要一起治理。幂等只能解决重复,不能自动解决旧事件覆盖新状态的问题。

最后,补偿任务要复用同一套幂等逻辑。很多事故发生在人工补偿脚本或定时重放任务,因为它们绕过了正常消费者。只要会产生业务副作用,就应该走统一幂等入口。

五、总结

Kafka 消费端要接受重复消息存在,重点是避免重复副作用。稳定幂等键、本地事务、外部调用幂等和失败分类,是消费系统可靠性的基础。重复消息不可怕,不可控的业务副作用才可怕。