背景
quartz
是一个功能丰富、开源、分布式的任务调用框架,我参与的很多项目都用它来实现定时调度功能。关于定时任务,有一个常见的需求是:由 Web 应用来控制定时任务的启动、停止、调度周期等。
本文探讨的是,对于当前正在调度的、耗时较长的任务,如果通过 Scheduler
类的 pauseJob、deleteJob、rescheduleJob
方法重新对该任务调度,是否会立即生效呢?
此时,当前这一轮 Job
类的 execute
方法会立即终止呢,还是等待这一轮任务完成后,任务新的调度操作才会生效,一起来看看。
任务调度 API
任务管理的停止、重启、重新调度,分别对应三个方法。
ini
复制代码
public static void stopJob(Scheduler scheduler) { try { JobKey jobKey = JobKey.jobKey("testJobId","testJobGroup"); scheduler.pauseJob(jobKey); logger.info("Stop test job ok."); } catch (SchedulerException e) { logger.error("停止定时任务异常!",e); } } public static void resumeJob(Scheduler scheduler) { try { JobKey jobKey = JobKey.jobKey("testJobId","testJobGroup"); scheduler.resumeJob(jobKey); logger.info("Resume test job ok."); } catch (SchedulerException e) { logger.error("停止定时任务异常!",e); } } public static void rescheduleJob(Scheduler scheduler) { TriggerKey triggerKey = TriggerKey.triggerKey("testJobId","testJobGroup" ); try { CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cornExp); //按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); Date rescheduleJob = scheduler.rescheduleJob(triggerKey, trigger); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); JobKey jobKey = JobKey.jobKey("testJobId", "testJobGroup"); logger.info(jobKey.getName() + " 已被重新安排执行于: " + sdf.format(rescheduleJob) + ",并且以如下规则重复执行: " + trigger.getCronExpression()); } catch (SchedulerException e) { e.printStackTrace(); } }
Job 的 execute 模拟长任务
定义一个由 @DisallowConcurrentExecution
注解的、非并行任务类,用 sleep
模拟长耗时的逻辑:
java
复制代码
@DisallowConcurrentExecution @Component public class VulnerCrawlerJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { logger.info("Start A job at "+new Date()); if(true) { try { Thread.sleep(300000); logger.info("Finished A job at "+new Date()); } catch (InterruptedException e) { e.printStackTrace(); } return; } } }
测试结果
任务调度通过页面控制,状态流转过程为:
- 应用启动,且到达调度时间点后,任务开始执行;
- 任务执行休眠 5分钟内,执行 stop 停止任务;
- 修改调度周期,再执行 rescheduleJob;
- 修改系统时间,使其为下一轮调度时间之前。
第一步,在任务启动且休眠的5分钟内,通过页面停止任务:
第二步,再开启任务,并修改执行周期:
第三步,修改系统时间为 2020-12-6 日 11:12 分,看看下一轮任务的执行周期是否发生变化。
查看日志结果:
序号②、③、④ 说明,11:13 分开始的任务执行期间,虽然重新调度了任务在 11:23 分执行,但是上一轮任务仍然继续,且在 11:18 分执行结束。
修改时间为 12月6日 11:11 分后,新一轮任务在 11:23 分执行,按新的周期。
启示录
反复验证后,可以得到这个结论:对于当前正在调度的、耗时较长的任务,如果通过 Scheduler
类的 pauseJob、deleteJob、rescheduleJob
方法重新对该任务角度,新的调度策略对本轮任务无效。
那么问题来了,怎么才能干预当前正在执行的 Job,使其可以即使终止呢?有一种思路是实现 InterruptableJob
,execute
执行过程中轮询中断标识。但是,对于没有循环逻辑、只有大量串行计算的任务来说,还是没有效。