Kratos.Consumer 默认启用自动提交(EnableAutoCommit: true)且间隔5秒,不感知业务耗时,导致处理超时+Rebalance时重复消费;须显式设为false,配合手动MarkOffset与业务事务一致,并实现幂等。

kratos.Consumer 里没配 EnableAutoCommit 却仍出现重复消费
这是 Kratos 消费端最隐蔽的坑之一。Kratos 的 kratos.Consumer 默认启用自动提交(EnableAutoCommit: true),但它的默认提交间隔是 5s,且不感知业务处理耗时。一旦某条消息处理超过 5 秒,而消费者又恰好在此期间触发了 Rebalance(比如滚动发布、GC STW、网络抖动),旧消费者还没来得及提交 offset,新消费者就会从上一个已提交位点开始拉取——这批“正在处理但未提交”的消息就被重复推送。
检查方法很简单:打开你的 consumer.go 或配置文件,确认是否显式设为 false;如果没设,就等于用了默认值 true。
- 不要依赖默认行为,始终显式声明
EnableAutoCommit: false - 配合
MaxPollIntervalMs调大(比如设为300000),避免因单条处理慢被踢出组 - 业务处理完再调用
c.MarkOffset(),确保提交和业务落库在同一个事务边界内(如用本地消息表或数据库事务兜底)
用 kratos.NewConsumer 启动后,日志里频繁出现 rebalance started
Kratos 本身不隐藏 Kafka 原生 Rebalance 日志,但如果你没开启 Kafka 客户端 debug 级别日志,就容易错过关键线索。Kratos 封装的 consumer 底层仍是 sarama 或 franz-go,Rebalance 频繁发生会直接导致重复消费窗口扩大。
先看日志里有没有连续出现的 rebalance completed 或 coordinator dead,再查监控里的 consumer-group-lag 是否伴随尖峰波动——这基本能锁定是心跳超时还是协调者失联。
- 调高
SessionTimeoutMs(建议 ≥ 45s)和HeartbeatIntervalMs(建议 ≤ 15s),避免 GC 或 CPU 抖动误判死亡 - 禁止在消费回调里做阻塞操作(如同步 HTTP 调用、长事务 DB 查询),否则心跳发不出去
- 检查 Kratos 的
WithGroupID是否被多个服务共用,不同服务用同一 GroupID 是 Rebalance 风暴的常见源头
业务逻辑里用了 c.MarkOffset(),但数据库还是写入了重复数据
c.MarkOffset() 只是把 offset 提交到 Kafka,它和你的数据库写入完全解耦。如果 DB 写成功了、MarkOffset() 却失败(比如网络断了、Kafka 不可用),下次重启照样重推——这就是典型的“offset 提交与业务非原子”问题。
不能靠 Kratos 框架自动修复这点,必须自己补业务幂等。Kratos 提供的是消费能力,不是业务语义保障。
- 优先用数据库唯一索引(如
order_id+event_type联合唯一)拦截重复插入 - Redis
SETNX做前置过滤时,key 必须包含业务主键+事件类型+时间窗口(比如evt:pay:123456:202610),避免 key 过期后漏判 - 别在
MarkOffset()成功后再去查 DB 判重——此时消息已算“消费完成”,重复已发生
排查时发现 kratos/consumer 没暴露原始 sarama.ConsumerGroup 实例
Kratos 的封装确实藏掉了底层 client,导致你没法直接调 consumer.Errors() 或注册 OnPartitionsRevoked 回调。这意味着你无法在分区被收回前主动保存当前处理进度,也无法捕获 rebalance 中断信号。
这不是 bug,而是设计取舍。Kratos 选择简化接口,代价是牺牲一部分底层可观测性。
- 临时方案:改用
franz-go原生 client + Kratos 的app.Run生命周期管理,自己控制 rebalance 逻辑 - 长期方案:在消费函数入口加日志打点,记录
msg.Topic、msg.Partition、msg.Offset和当前时间戳,配合 Kafka 自带的kafka-consumer-groups.sh --describe对比 lag 和实际处理位点 - 最关键的一点:永远假设 Kratos consumer 可能在任意时刻丢弃消息并重推,业务层不能依赖“只来一次”这个前提


















