Kafka 消息重复消费的三种真实成因,以及幂等该怎么落
先说结论:Kafka 只保证 at-least-once,重复是设计的一部分,消费端必须自己做幂等,别指望 broker 帮你解决。
重复最常见的三种成因。
第一种,offset 提交晚了。处理完业务逻辑、还没提交 offset 的时候进程重启或者 rebalance,这批消息会被重新投递。手动提交模式里把 commit 放在业务事务之后是常规做法,但事务和 offset 没法原子提交,中间那段窗口天然会重复。
第二种,rebalance 期间的重复投递。消费者组里有人上下线,分区重新分配,正在处理但未提交的那批消息会被新消费者接走。会话超时设得太短、处理一条消息耗时超过 max.poll.interval.ms,都会让消费者被判死,触发 rebalance。
第三种,生产端重试导致的重复。acks=all 加 retries>0,broker 已经写入但响应丢了,生产者重发,同一条消息进了两次。这个可以开幂等生产者(enable.idempotence=true),用 PID + sequence number 让 broker 去重,但只在单分区单会话内有效,跨会话还是会有。
幂等的落地方式按成本排:
一,业务唯一键 + 数据库唯一索引。最简单也最可靠,把消息里的业务单号建唯一索引,重复插入直接报冲突,捕获后当成功处理。
二,Redis 去重表。用消息 id 做 key,SETNX 加过期时间,先占坑再处理。注意过期时间要覆盖业务重试窗口,否则过一会儿又漏了。
三,状态机。把业务流转设计成只能单向推进,处理前先判断当前状态,已经是目标状态的直接跳过。这种方式不需要额外存储,但要求业务本身是幂等的。
四,分布式锁。同一个业务键加锁串行处理,锁的粒度要小,否则吞吐直接掉下来。
最后提醒一句:做幂等的前提是消息能拿到稳定的唯一标识。如果生产端没带业务单号,只靠 offset 是不行的,因为 rebalance 之后 offset 会指向不同的消息。发消息的时候就把 traceId 或单号带上,后面所有的排查都靠它。
- 点赞
- 收藏
- 关注作者
评论(0)