Quartz加@Transactional后triggerJob触发任务延迟被MisfireHandler处理
问题背景
定时任务逻辑非常简单,仅在控制台输出当前时间,需要实现一个按钮用于立即触发该任务执行。
相关代码
XxxService.class
@Override @Transactional(rollbackFor = Exception.class) public void run(Long[] jobIds) { for(Long jobId : jobIds){ ScheduleUtils.run(scheduler, this.getById(jobId)); } }
ScheduleUtils.class
public static void run(Scheduler scheduler, ScheduleJobEntity scheduleJob) { try { JobDataMap dataMap = new JobDataMap(); dataMap.put(ScheduleJobEntity.JOB_PARAM_KEY, scheduleJob); System.out.println(Thread.currentThread()+"need hello..."+new Date()); scheduler.triggerJob(getJobKey(scheduleJob.getJobId()), dataMap); } catch (SchedulerException e) { throw new RRException("run fail", e); } }
MyJob(HelloJob)
@Data @Component("HelloJob") public class HelloJob extends CustomJob { public String jobDescription = "演示Job"; @Override public void run(String params) { System.out.println( Thread.currentThread() +"hello..."+new Date() + "params: " + params); } }
复现现象
- 在
run(Long[] jobIds)方法上添加*@Transactional*注解时,任务不会立即执行,通常延迟15~20秒才会运行,日志输出如下:
Thread[http-nio-8500-exec-1,5,main]need hello...Mon Jun 27 12:55:02 CST 2022 2022-06-27 12:55:24 INFO [bear] QuartzScheduler_BearScheduler-DESKTOP-521BOOH1656305645549_MisfireHandler org.springframework.scheduling.quartz.LocalDataSourceJobStore Handling 1 trigger(s) that missed their scheduled fire-time. Thread[BearScheduler_Worker-3,5,main]hello...Mon Jun 27 12:55:24 CST 2022params: aaaa,bbbbbbb
- 移除
run(Long[] jobIds)方法上的*@Transactional*注解后,任务可正常立即执行,日志输出如下:
Thread[http-nio-8500-exec-4,5,main]need hello...Mon Jun 27 12:59:40 CST 2022 Thread[BearScheduler_Worker-6,5,main]hello...Mon Jun 27 12:59:40 CST 2022params: aaaa,bbbbbbb
原因说明
核心问题是Quartz使用JDBC持久化作业存储(即配置的LocalDataSourceJobStore)时,加了@Transactional后出现事务可见性问题:
- 调用
scheduler.triggerJob()时,Quartz会直接向数据库插入一条临时触发的任务记录,但因为外层Service方法加了@Transactional,这个插入操作会加入当前方法的事务,不会立刻提交到数据库。 - Quartz的调度线程靠定时轮询数据库拉取待执行任务,未提交的事务记录对调度线程不可见,所以它根本感知不到即时触发任务的请求,自然不会马上调度。
- 等整个
run方法执行完、事务提交时,这条触发记录才真正持久化到数据库。此时距离调用triggerJob已经过去一段时间,Quartz下一次轮询扫到这条记录时,发现记录里的计划触发时间早于当前时间、超过了misfire阈值,就会判定这个任务是“错过触发的任务”,交给MisfireHandler处理,这时候才会真正执行任务,也就是看到的15~20秒延迟。
去掉@Transactional之后,triggerJob触发的数据库插入操作没有外层事务包裹,执行完就立刻提交,Quartz调度线程很快就能扫到这条待执行记录,任务自然就立即运行了。
解决方案
- 如果
run方法里没有其他需要事务保证一致性的数据库写操作,直接移除方法上的@Transactional注解即可,和验证的效果一致。 - 如果方法里确实有其他必须放在事务里的数据库操作,可以把Quartz任务触发的逻辑挪到事务提交完成之后再执行,比如通过Spring的事务同步管理器注册提交后回调,在回调里调用
ScheduleUtils.run触发任务,就能保证触发记录写入数据库后立刻对Quartz可见,不会出现misfire延迟。
内容的提问来源于stack exchange,提问作者HoRinbb
相关产品推荐
相关产品推荐

