Kafka重平衡引发重复消费是否属于预期行为?
Kafka重平衡场景下的重复消费是否属于预期行为?
场景概述
- 部署架构:两个应用实例分别部署在独立EC2上,各有一个同消费组ID的Kafka消费者
- 消费配置:使用
Spring BatchAcknowledgingMessageListener同步批量消费,消费完成后调用Acknowledgment.acknowledge();单线程ConcurrentMessageListenerContainer,AckMode为MANUAL_IMMEDIATE,启用syncCommits - 触发流程:
- 关闭其中一个实例,消费者退出消费组,触发重平衡,剩余消费者接管所有分区
- 重启该实例,消费者重新加入消费组,再次触发重平衡,两个消费者各获取半数分区
- 出现少量消息被两个消费者重复消费的情况
- 异常日志特征:被撤销分区的消费者,在分区撤销日志之前出现offset提交失败的日志:
2022-11-17 08:24:21,809 INFO [consumer-0-C-1] o.a.k.c.c.i.ConsumerCoordinator [ConsumerCoordinator.java:1156] [Consumer clientId=consumer-xms-batch-mt-callback-3, groupId=xms-batch-mt-callback] Failing OffsetCommit request since the consumer is not part of an active group
- 现象特性:中等流量(每秒1000条记录)下偶发;无重复消费时,日志顺序为「分区撤销日志 → offset提交失败日志」
核心结论
这种重复消费不属于预期行为,本质是重平衡过程中offset提交与分区撤销的竞态条件导致的时序异常,结合配置细节,具体触发逻辑如下:
- 同步消费的时序偏差:
单线程同步消费的正常流程是「轮询拉取消息 → 消费处理 → 提交offset → 下一轮询」,分区撤销通知本应在轮询阶段触发。但在高负载场景下,会出现时序错位:
- 消费者处理完一批消息后调用
acknowledge(),触发同步offset提交请求,但此时消费组重平衡已启动,该消费者已被标记为非活跃成员,导致提交失败 - 由于offset未成功提交,重平衡后接管该分区的消费者会从上次已提交的offset位置开始消费,之前已处理但未提交offset的消息就会被重复消费
- 配置组合的影响:
MANUAL_IMMEDIATE模式下,acknowledge()会立即触发offset提交,而非延迟批量提交,缩短了提交操作的时间窗口,但也让提交操作更易与重平衡触发时机冲突- 启用
syncCommits意味着提交请求需要等待Kafka Broker的响应确认,进一步拉长了提交操作的耗时,增加了与重平衡竞态的概率
- 日志顺序异常的指向:
正常情况下分区撤销日志应先于提交失败日志出现,异常场景下顺序反转,说明在消费者处理分区撤销通知前,offset提交请求已被发送,这是重平衡与提交操作的竞态条件导致的偶发现象。
建议排查方向
- 检查Kafka Broker的重平衡相关超时配置(如
group.max.session.timeout.ms、group.min.session.timeout.ms),确保消费者有足够时间完成offset提交后再处理重平衡 - 为消费者添加优雅停机逻辑,在实例关闭时等待正在处理的消息完成offset提交,再退出消费组
- 考虑升级kafka-clients(当前3.1.1)与spring-kafka(当前2.8.6)到较新的稳定版本,后续版本可能针对重平衡与offset提交的竞态问题做了优化
内容的提问来源于stack exchange,提问作者Mikael Carlstedt
相关产品推荐
相关产品推荐

