本文已参与「新人创作礼」活动,一起开启掘金创作之路。
kafka 后端埋点数据实时同步到 GP集群,出现重复消费问题有两方面的原因。
问题一
由于gp集群的并发量限制,每到任务提交的高峰期,执行一条sql并返回结果需要排队很长时间,一旦排队时间大于5分钟, 就会大于kafka consumer 两批poll(long)方法之间默认的处理时间。处理线程会认为此consumer 挂掉, 进而引发rebalance,导致重复消费数据。 解决方案: 通过调大两次批处理之间的间隔来跨越GP集群在任务高峰期的等待时间,以此来提高容错能力。参数为:max.poll.interval.ms
问题二
由于可能某个批次时间段之内需要通过poll(long)方法拉取的数据比较大,在默认的两次心跳间隔之内不能处理完数据, 心跳线程会认为此consumer死掉,进而引发rebalance,导致重复消费数据。 解决方案: 调大两次心跳进程之间的时间间隔,提高容错能力,参数据为: session.timeout.ms 与heartbeat.interval.ms
重点释惑采坑记
在Kafka 0.10.0.0(含)版本之前只有session.timeout.ms参数,在这之后引入了max.poll.interval.ms参数。 通过后台心跳线程与处理线程将heartbeats与poll()的调用解耦.这允许比心跳间隔更长的处理时间(即,两次连续轮询poll()之间的时间)。 由于我以前的开发版本基本上都是基于kafka 0.10.0.0之前的版本,所以在这里踩了一个大坑。
避免重复消费的处理
如果只有以上处理也不能避免kafka重复消费的问题,只能尽量减少rebalance,以此来减少重复消费。因此我又坐了下面的处理 想要避免发生kafka consumer rebalance时的重复消费问题,只能把每条数据的处理与kafka consumer偏移量的管理放到一个事物内或者 保证两次处理之间的原子性。 但是由于针对我们的场景,想方设法把每条数据的处理与kafka consumer偏移量的管理放到一个事物内的代价太大(需要大量IO交互和大量的与GP集群的交互), 因此我把每次消费完以后kafka consumer 的偏移量存储到外部设备,尽量做到在一个事物内。这样当kafka 发生rebalance时,就会从外部设备重新读取偏移量, 从而避免重复消费数据。 这样虽然没有把对每条数据的处理与kafka consumer偏移量的管理放到一个事物内,保证恰号一次(exactly-once)的语义。 但经过大量测试数据没有发生重复消费的问题。由于没有保证恰号一次语义,发生重复消费的几率应该会存在,但也是微乎其微, 数据的准确率会达到99.999%以上。理论上数据不会丢失,实际上数据也没有发生丢失的情况。
目前重复消费的问题说明
3.24号08:40左右发现有数据重复消费的情况出现。经过排查验证是kafka服务源端数据发生了重复导致的, 经验kafka运维人员沟通,他们的策略是是尽量保证不丢,可以允许有少量重复。
后记
由于我是每消费一条或者一批数据都会都会做一次偏移量的存储,可能会有人就会认为我做了大量不必要的开销,浪费了大量IO操作。 因为网上很多关于kafka的文章关于在ConsumerRebalanceListener这个监听类的onPartitionsRevoked方法内做偏移量的存储工作。 经过测试这里会有一个问题,就是当kafka的consumer的增加或者kafka的分区数增加引起的Rebalance,这样做没问题, 但是如果是kafka consumer的减少引发的Rebalance时候, 由于此consumer 已经挂掉,不能调用ConsumerRebalanceListener的onPartitionsRevoked方法 去存储当前分区消费的偏移量,就会导致重复消费。
参考: Kafka session.timeout.ms heartbeat.interval.ms参数的区别以及对数据存储的一些思考