使用线程池消费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 也是毫无压力。
浙公网安备 33010602011771号