背景:
组件式开发过程中常会遇到一些自己不需要的组件开启了太多的kafka监听的情况,会带来以下问题:
kafka监听中打印太多日志,影响自己查看自己的日志信息,不利于排查定位问题。
kafka监听频繁消费消息,占用大量时间片,影响项目响应效率,严重者还会导致启动会长时间无法正常响应请求。笔者就遇到大量无用kafka监听导致程序启动后一个多小时才能正常提供服务的情况。
目标:
因此,停止项目中自己不需要的kafka监听就变得尤其重要。我们希望:
程序启动时不开启任何监听。
需要开启kafka监听时,支持手动开启和关闭。
解决思路:
通过阅读spring源码我们发现,spring启动过程中kafka监听的启动过程如下:
1、KafkaListenerEndpointRegistrar实现了InitializingBean接口,初始化bean的时候会执行该接口的afterPropertiesSet()方法,在该方法中注册所有带有@KafkaListener的监听。
2、在KafkaListenerEndpointRegistry类实现了SmartLifecycle接口,SmartLifecycle 是一个接口。当Spring容器加载所有bean并完成初始化之后,会接着回调实现该接口的类中对应的方法(start()方法)。在start方法中会启动监听。我们要做的就是重写该方法,在项目启动时不启动监听。
3、在KafkaBootstrapConfiguration配置类中注册KafkaListenerEndpointRegistry类的实例,实例名为:
org.springframework.kafka.config.internalKafkaListenerEndpointRegistry。这样每次程序启动,都会自动启动全部监听。
于是,我们决定重写KafkaListenerEndpointRegistry类中的start方法,具体过程如下:
写一个类:TransitAlarmKafkaListenerEndpointRegistry继承KafkaListenerEndpointRegistry,重写start,在start方法中不启动监听。
@Slf4j public class TransitAlarmKafkaListenerEndpointRegistry extends KafkaListenerEndpointRegistry { @Override public void start() { log.info("此处不自动启动kafka监听,请手动启动"); } }
在配置类中使用@Bean注入该类的实例,命名为:
@Primary @Configuration public class TransitAlarmKafkaListenerEndpointRegistryConfig { @Bean(value = "org.springframework.kafka.config.internalKafkaListenerEndpointRegistry") public TransitAlarmKafkaListenerEndpointRegistry kafkaListenerContainerFactory() { return new TransitAlarmKafkaListenerEndpointRegistry(); } }
,这样在程序启动是会用TransitAlarmKafkaListenerEndpointRegistry代替KafkaListenerEndpointRegistry类的实例注入到KafkaBootstrapConfiguration中。
***********************************************************************************************************
此时,已经实现了程序启动时不启动任何监听。下面,还需要能够支持手动开启和关闭监听,具体过程如下:
启动监听:通过KafkaListenerEndpointRegistry的实例调用根据listenerId获取监听,调用start方法启动:
@Autowired private KafkaListenerEndpointRegistry registry; /** * 开启监听. * * @param listenerId 监听ID */ @Override public void startListener(String listenerId) { //判断监听容器是否启动,未启动则将其启动 if (!registry.getListenerContainer(listenerId).isRunning()) { registry.getListenerContainer(listenerId).start(); } }
2.停止监听:通过KafkaListenerEndpointRegistry的实例调用根据listenerId获取监听,调用stop方法停止:
@Override public void stopListener(String listenerId) { registry.getListenerContainer(listenerId).stop(); }
至此,真正实现了项目启动时不启动任何监听,可以手动开启和停止监听。