需求
一个给客户实时回传数据的需求,需要从kafka消费并http发送给客户,采用了kafka consumer多线程消费的方式。
多线程方案
首先,kafkaConsumer类不是线程安全的,不能在多线程中共享kafkaConsumer实例,否则会抛出异常。但是KafkaConsumer 中有个方法是例外的,它就是 wakeup(),你可以在其他线程中安全地调用 KafkaConsumer.wakeup() 来唤醒 Consumer。 目前有两种常见的consumer多线程消费方案:
- 启动多个kafkaconsumer实例(线程),进行完整的消费,处理流程。
- 启动多个kafkaconsumer实例(线程),同时使用多个线程进行消息处理流程。
第一种方式会受到kafka topic分区数的限制,第二种实现难度高,单分区无法保证消费顺序。由于我们的线上topic有160个分区,评估分区数应该不是限制,所以采用第一种方案。
Rebalance
consumer group重平衡的问题在哪种方案中都难以避免,重平衡过程中所有消费者都暂停了消费,开销很大。那么我们是否可以避免重平衡问题呢?
重平衡有三个触发的原因:
- 消费者组数量发生变化
- 订阅topic数量发生变化
- 订阅topic的分区数发生变化
一般来讲给后面两种情况是主动操作,也是难以避免的,所以主要关注第一种情况出现的原因:
- 没有及时发送心跳,导致consumer被踢出group。
session.timeout.ms:推荐设置为6s consumer存活的时间间隔。
heartbeat.interval.ms: 推荐设置为2s 发送心跳的间隔。目前Coordinator通知各个Consumer实例开启 Rebalance 的方法,就是将 REBALANCE_NEEDED 标志封装进心跳请求的响应体中。 - Consumer消费时间过长,会被踢出group,提交位移会抛出CommitFailedException。如果是本身正常消费时间就大于max.poll.interval.ms,最后会导致所有consumer都被踢出
max.poll.interval.ms:两次poll之间的时间间隔
max.poll.records:一次拉取的最大record数量
实现
在实际使用中,rebalance前要提交系统的offset,rebalance后要更新分配的分区
consumer.subscribe(Arrays.asList(topic), new ConsumerRebalanceListener() {
@Override
public void onPartitionsRevoked(Collection<TopicPartition> collection) {
logger.info("before rebalance,commit offset");
consumer.commitSync();//同步提交位移
}
@Override
public void onPartitionsAssigned(Collection<TopicPartition> collection) {
logger.info("rebalance after");
init();//更新分配的分区
}
});