在一套集群应用(4个实例节点)启动的时候,应用调用了QuartzScheduler#scheduleJob初始化定时任务,会偶然触发 JobPersistenceException: could not store trigger : unique constraint violdated .即多个节点同时scheduleJob的时候了违反约束条件.
问题代码大概如下:
public class MyQuartzConfig { @Autowired private Scheduler scheduler; public void init() { scheduler.scheduleJob(job, trigger); } } 复制代码
我们都知道,Quartz集群模式是借用了数据库来实现竞争锁的,如果某个节点获取锁失败,会等待一段时间后再次尝试,直到超过重试次数抛出异常.上述出现违反约束条件肯定是因为锁没有生效,换句话说事务没有生效
Quartz提供了两个持久化任务的实现类,分别是 JobStoreCMT 和 JobStoreTX
JobStoreCMT的两个数据源怎么理解呢?根据官方注释可以看到,TxDataSource事务受容器管理,可应用于JTA等XA场合. 而NonTxDataSource事务不受容器管理,是Quartz自行管理.
小结:CMT的NonTxDataSource即是JobStoreTX的数据源, Quartz获取connection后会设置autoCommit=false,由Quartz自行控制事务 .
而JobStoreCMT的TxDataSource是额外添加的,为的是让用户能够控制这些事务,给用户更大的灵活性.
JobStore在不同的场景下都会用到这两种数据源,分别是 executeInLock 和 executeInNonManagedTXLock
当Spring接管Quartz后,情况会发生一些变化. 如果Spring提供的SchedulerFactoryBean设置了数据源,就会用LocalDataSourceJobStore来替代Quartz的JobStore类.
LocalDataSourceJobStore其实是一个JobStoreCMT,它的TxDataSource受Spring事务管理,NonTxDataSource则不会. 同时他会将从TxDataSource获取的connection.autoCommit=ture看到这里应该能猜到原因了,在我们的例子里,调用scheduleJob方法使用的数据源是TxDataSource,而持久化类LocalDataSourceJobStore默认autoCommit=ture,在这种情况下 其实我们调用时是不存在事务的 ,自然就会出现文中的问题.
没有事务的话给它加上事务就好了,由于我们用到Spring-Quartz,加上Spring的事务就能解决问题
public class MyQuartzConfig { @Autowired private Scheduler scheduler; @Transactional // 加上事务注解即可解决问题 public void init() { scheduler.scheduleJob(job, trigger); } } 复制代码
Quartz提供JobStoreCMT是为了让用户调用Quartz的时候有事务控制(例如希望多个Quartz实例同时成功或同时失败).
我也不知道以后怎么避免这类问题,Spring提供的文档好像没有说明事务的问题,难道真的只能好好看注释了?