一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情
场景
消费者消费消息之后,会提交偏移量到broker,kafka默认配置是自动提交,在以下场景就会出现消息重复消费的场景:
1. 消息在阈值时间内没有消费完毕
正常情况下,consumer 在“⾃动提交超时时限”内消费完⼀批消息后会⾃动提交 offset。但当 consumer 消费能⼒⽐较低时,其取出的⼀批消息在阈值时间内没有消费 完毕,此时 consumer 会向 broker 提交⼀个异常。此时 broker 不会认为该 consumer 宕机, 因为当前 consumer 向 broker 提交了信息。
对于 consumer 来说,由于本次消费过程在时限内没有完成,即没有成功,所以该 consumer 会再从该 partition 中拉取消息。由于前⾯没有提交 offset,所以这次拉取的消息与上次的是相同的。该consumer⼜重新消费。
当Consumer由于消费能⼒较低⽽引发了消费超时时,则可能会形成重复消费。
2. Consumer 在消费过程中,应⽤进程被强制kill掉或发⽣异常退出
例如在⼀次poll了500条消息,消费到200条时,进程被强制kill,导致offset 未提交,或出现异常退出导致消费到offset未提交。下次重启时,依然会重新拉取这500消息,这样就造成之前消费到200条消息重复消费了两次。因此在有消费者线程的应⽤中,应尽量避免使⽤kill -9这样强 制杀进程的命令。
3. Rebalance场景下
该场景涉及到消费者创建时会有一个属性max.poll.interval.ms,代表kafka消费者在每一轮poll()调用之间的最大延迟,如果此超时时间期满之前poll()没有被再次调用,则消费者被视为失败,并且触发Rebalance,分组将重新平衡,以便将分区重新分配给别的消费者,这样已经被前一个消费者消费的消息,就会被Rebalance后的消费者再消费一次,产生重复消费。
解决方案
方案1:提⾼消费能⼒
提⾼单条消息的处理速度
- 对消息处理中⽐ 较耗时的步骤可通过异步的⽅式进⾏处理、 利⽤多线程处理等。
- 在缩短单条消息消费时常的同时,根据实际场景可将max.poll.interval.ms值设置⼤⼀点,避免不必要的rebalance,
- 此外可适当减⼩max.poll.records的值,默认值是500,可根据实际消息速率适当调⼩。
(注意:这种思路可解决因消费时间过⻓导致的重复消费问题, 对代码改动较⼩,但⽆法绝对避免重复消费问题。)
方案2:改为⼿动提交
设置消费者手动提交偏移量:
spring.kafka.consumer.enable-auto-commit=false
方案3:引⼊消费者单独去重机制
每条消息设置唯一key值,消息被消费后保存状态到redis等中间件,新消息过来后进行校验
结束
需要交流学习可以关注公众号【温故知新之java】,互相学习,一起进步