背景
最近团队的小伙伴反馈了几个关于kafka消费者的问题
- 项目监听了几十个topic,启动时都需要进行初始化,很多时候本地调试时根本不需要启动消费者,但却被迫进行初始化,希望能优化这个问题。
- 由于我司在每个环境上,都区分了不同集群(如01、02、03等),来满足并行开发不同需求使用。但目前kafka并未根据不同集群做区分,导致partition消息无法被指定的某个集群消费,例如原本希望被集群01消费的消息,却被集群03消费了,所以希望能服务运行时动态控制不同集群消费者是否开启;
- kafka的topic调整partition数量后,消费者服务在不增加实例的前提下,想调整消费者服务的并发数只能手动修改代码后重新部署,非常麻烦,所以希望能在服务运行时动态调整消费者的并发数量,提高消费能力;
分析
我司项目都是基于SpringBoot,消费者使用@KafkaListener注解实现监听。
问题1
由于@KafkaListenr注解被KafkaListenerAnnotationBeanPostProcessor扫描,在postProcessAfterInitialization方法被解析后构造Consumer容器,且autoStartup属性默认为true,所以会自动启动。
要禁止自动启动Consumer容器比较简单,只要将Consumer容器设置成不自动启动即可,有以下解决方案:
- 在@KafkaListener注解指定autoStartup属性为false,该方法只针对使用注解的方法;
- 在@KafkaListener注解指定containerFactory属性,该属性值指向AbstractKafkaListenerContainerFactory实现类的实例,实例可以设置autoStartup属性为false;
在本地调试环境时可以将autoStartup属性设置为默认false不启动Consumer容器,但是在其他环境,还是需要启动Consumer容器的,所以就有以下两种方案
- 利用不同环境的配置文件,根据配置值来决定是否启动Consumer容器(@KafkaListener的autoStartup支持SpEL);
- 指定AbstractKafkaListenerContainerFactory的autoStartup为false,集成Apollo配置中心,并且在Spring容器完全启动成功后,再根据配置中心文件判断是否需要对Consumer容器进行start;
问题2
kafka通过Rebalance协议来规定一个Consumer Group下的所有Consumer如何达成一致,来分配订阅Topic的每个partition。
触发Rebalance的条件有3个:
- Group组员发生变化,例如有Consumer加入或离开Consumer Group;
- 订阅的Topic数量发生变化;
- 订阅的Topic的partition数量发生变化;
综上可以得知,我们只需让指定集群的Consumer在启动的时候,不加入Consumer Group(不启动Consumer),或者在已经加入Consumer Group前提下离开Consumer Group即可,从而触发Rebalance机制,最终达到只让指定集群的Consumer消费的目的。
至于想要在服务运行时进行动态调整,只需接入Apollo配置中心,不同集群使用不同的配置文件即可。
问题3
@KafkaListener注解上的concurrency属性,用来指定Consumer的并发数量。
目前项目想调整Consumer的并发数量,只能通过手动修改的代码后重新发布,其目的本质上来讲,是修改
AbstractKafkaListenerContainerFactory实例上的concurrency属性,然后重启AbstractKafkaListenerContainerFactory实例(其实就是调用stop()方法后再调用start()方法),最终在启动的时候,消费者服务会根据concurrency启动相应数量的消费者。
至于想要在服务运行时进行动态调整,只需配合Apoll配置中心实现即可。
实现
在问题1的两种方案中,由于方案2可以更灵活控制,所以此处直接使用方案2
typescript复制代码@Bean(KAFKA_CONSUMER_FACTORY)
public KafkaListenerContainerFactory<?> kafkaConsumerContainerFactory() {
Map<String, Object> properties = new HashMap<>();
//...其他配置
DefaultKafkaConsumerFactory<O