起因
某日,业务开发同事反馈生产环境基于quartz实现的调度任务平台突然出现大量任务未按时执行,执行时间延迟严重甚至不执行,经排查确认为配置的短周期调度任务数量过多,在短周期内quartz的调度线程QuartzSchedulerThread从对应的执行线程池中获取不到可用线程进入等待阻塞中,在等待中周而复始不断的有任务需要执行,最终进入死循环导致大量任务未执行。
quartz源码分析
quartz中的线程主要分为调度线程和执行线程(执行线程对应一个线程池SimpleThreadPool) QuartzSchedulerThread.java org.quartz.core.QuartzSchedulerThread#run 即是quartz的主要调度线程,该线程执行主要分为以下几个步骤
1.每隔30s 定时执行该线程
2.获取执行线程池是否有可用线程,如果线程池满了则阻塞等待直到有可用线程返回
3.查询待触发执行的trigger,到时间点定时执行
处理方案
1.合理调整缩减quartz执行job业务耗时,以便于线程快速执行放回线程池重新利用。
2.合理调整quartz执行线程线程池大小,可以通过调整quartz配置的org.quartz.threadPool.threadCount= ?参数调整
3.随着业务的激增以及短周期任务数量增多,即使我们不断横向扩容增加实例,调度任务的延迟仍然会不断增加。原因为Quartz同一时间点只能由一个节点的调度线程QuartzSchedulerThread使用例如mysql的行级排他锁 for update抢占查询出1组Trigger,交给执行工作线程运行,所以如果要提高quartz处理程序的并发量,降低延迟,单纯扩实例作用不大!查阅quartz文档此时可采用多个scheduler,指定不同的表前缀采用多套quartz表来以类似分表的操作来提升性能。
//可以配置多个bean
SchedulerFactoryBean schedulerFactoryBean = 设置quartz配置信息
schedulerFactoryBean.afterPropertiesSet();
schedulerFactoryBean.start();