使用线程池消费kafka数据,性能提高50倍

问题描述:之前在springboot中监听kafka的topic,执行消费数据、数据同步的业务,但由于单线程并且有数据库的更新、删除操作、kafka的同步提交偏移量导致性能低下、且kafka同步提交导致程序运行一段时候后会出现:
max-poll-records相关的错误。这个是由于之前项目配置的批量拉取每次一万条导致。max-poll-records: 10000。后来把没次拉取调整为5000、1000、500、100。但是每次隔几分钟还是会出现这个错误。
直到把值改成20后,才能稳定消费,但是这个性能太低下了。经过测试一晚上才能消费几千条数据(可能频繁与数据库交互导致性能低下)。

优化步骤:
1、创建线程池
1     private final ExecutorService kafkaMessageExecutor = new ThreadPoolExecutor(
2             100, // 增加核心线程数
3             300, // 增加最大线程数
4             60L,
5             TimeUnit.SECONDS,
6             new LinkedBlockingQueue<>(2000), // 增加队列容量
7             new ThreadFactoryBuilder().setNamePrefix("kafka-consumer-pool-%d").build(),
8             new ThreadPoolExecutor.CallerRunsPolicy()
9     );

2、使用@PostConstruct注解,在项目启动的时候加载相关字典


    @PostConstruct
    public void init() {
        // 初始化字典缓存
        dictCache = CacheBuilder.newBuilder()
                .maximumSize(100)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(new CacheLoader<String, List<SysDict>>() {
                    @Override
                    public List<SysDict> load(String typeCode) {
                        return sysDictService.getSysDictByTypeCode(typeCode);
                    }
                });
    }

3、使用线程池消费数据,并且使用kafka的异步提交偏移量


 1     /**
 2      * 告警数据同步执行器
 3      * @param record Kafka消息记录
 4      * @param ack 消息确认
 5      */
 6     @KafkaListener(topics = "mysql_log", groupId = "test")
 7     public void alarmSyncHandler(ConsumerRecord<String, String> record, Acknowledgment ack){
 8         if(enableSyncData){
 9             log.info("告警同步:----------获取同步消息-----------: {}", record.value());
10 
11             kafkaMessageExecutor.submit(()->{
12                 try {
13                     this.alarmSync(record.value());
14                     // 异步提交,提升性能
15                     ack.acknowledge();
16                 } catch (IOException e) {
17                     log.error("告警同步:kafka数据解析失败!!!:{}", record.value());
18                     e.printStackTrace();
19                 } catch (Exception e) {
20                     log.error("告警同步:数据同步失败,请检查数据库!!!");
21                     e.printStackTrace();
22                 }
23             });
24         }
25     }

4、使用@PreDestroy注解结合interrupt()方法优雅关闭线程池


 1     /**
 2      * 应用关闭时自动调用,优雅关闭线程池
 3      */
 4     @PreDestroy
 5     public void shutdown() {
 6         log.info("正在关闭Kafka消息处理线程池...");
 7         kafkaMessageExecutor.shutdown();
 8         try {
 9             // 等待最多60秒让现有任务完成
10             if (!kafkaMessageExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
11                 log.warn("线程池任务在60秒内未能完成,尝试强制关闭");
12                 kafkaMessageExecutor.shutdownNow();
13                 // 再等待60秒
14                 if (!kafkaMessageExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
15                     log.error("线程池未能成功关闭");
16                 }
17             }
18         } catch (InterruptedException e) {
19             log.error("关闭线程池时被中断", e);
20             kafkaMessageExecutor.shutdownNow();
21             Thread.currentThread().interrupt();
22         }
23         log.info("Kafka消息处理线程池已关闭");
24     }

5、结论

经过测试,优化后的kafka消费性能比之前提高50倍有余。并且把拉取设置成:max-poll-records: 10000 也是毫无压力。

 

 
posted on 2025-08-06 14:32  代码吴彦祖  阅读(41)  评论(0)    收藏  举报