Spring Quartz Scheduler 竞争条件
Spring Quartz Scheduler race condition
我怀疑问题出在 SchedulerFactoryBean 的 setOverwriteExistingJobs 没有提供足够的保护。
一个节点将初始化调度程序,它将决定替换触发器(断点 org.quartz.impl.jdbcjobstore.SimpleTriggerPersistenceDelegate#deleteExtendedTriggerProperties)
在执行此方法后,触发器将不再存在于数据库中,因此当集群中的另一个节点尝试读取它时(org.quartz.impl.jdbcjobstore.JobStoreSupport#retrieveTrigger)它将失败并出现异常以下。由于这个异常,整个应用程序将无法启动(不仅仅是调度程序)。
Caused by: org.quartz.JobPersistenceException: Couldn't retrieve
trigger: No record found for selection of Trigger with key:
可以在 https://github.com/apixandru/case-study/tree/master/spring-boot-quartz/logs 找到日志
(第4次重启后在Server-1节点可以发现异常)
对于演示此问题的整个项目,请转至 https://github.com/apixandru/case-study/tree/master/spring-boot-quartz
我们配置调度程序的方式在这里
@Bean
JobDetailFactoryBean jobFactoryBean() {
JobDetailFactoryBean bean = new JobDetailFactoryBean();
bean.setDurability(true);
bean.setName("Sampler");
bean.setJobClass(SampleJob.class);
return bean;
}
@Bean
SimpleTriggerFactoryBean triggerFactoryBean(JobDetailFactoryBean jobFactoryBean) {
SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean();
bean.setName("Sampler Trigger");
bean.setRepeatInterval(20_000);
bean.setJobDetail(jobFactoryBean.getObject());
return bean;
}
@Bean
SchedulerFactoryBean schedulerFactoryBean(SimpleTriggerFactoryBean triggerFactoryBean, DataSource dataSource, Dependency dependency) {
Properties props = new Properties();
props.put("org.quartz.scheduler.instanceId", "AUTO");
props.put("org.quartz.jobStore.isClustered", "true");
SchedulerFactoryBean bean = new SchedulerFactoryBean();
bean.setTriggers(triggerFactoryBean.getObject());
bean.setSchedulerName("Demo Scheduler");
bean.setSchedulerContextAsMap(Collections.singletonMap("dependency", dependency));
bean.setOverwriteExistingJobs(true);
bean.setDataSource(dataSource);
bean.setQuartzProperties(props);
return bean;
}
这种情况在我们的工作服务器上经常发生,但在本地更难获得(可能是因为实际服务器是专用的并且比我的本地机器有更多的能力?)
要在任何机器上找到错误,以调试模式启动一个服务器并在 SimpleTriggerPersistenceDelegate.deleteExtendedTriggerProperties 上放置一个断点,然后在它执行后立即启动第二个服务器,你将得到这个异常
无论如何,在大约 40 次重新部署到我的本地集群 weblogic 服务器后,我在本地也遇到了这个错误。
问题在于默认情况下不使用事务管理器,因此不使用锁定。
解决问题需要调用schedulerFactoryBean的setTransactionManager方法
我怀疑问题出在 SchedulerFactoryBean 的 setOverwriteExistingJobs 没有提供足够的保护。
一个节点将初始化调度程序,它将决定替换触发器(断点 org.quartz.impl.jdbcjobstore.SimpleTriggerPersistenceDelegate#deleteExtendedTriggerProperties)
在执行此方法后,触发器将不再存在于数据库中,因此当集群中的另一个节点尝试读取它时(org.quartz.impl.jdbcjobstore.JobStoreSupport#retrieveTrigger)它将失败并出现异常以下。由于这个异常,整个应用程序将无法启动(不仅仅是调度程序)。
Caused by: org.quartz.JobPersistenceException: Couldn't retrieve trigger: No record found for selection of Trigger with key:
可以在 https://github.com/apixandru/case-study/tree/master/spring-boot-quartz/logs 找到日志 (第4次重启后在Server-1节点可以发现异常)
对于演示此问题的整个项目,请转至 https://github.com/apixandru/case-study/tree/master/spring-boot-quartz
我们配置调度程序的方式在这里
@Bean
JobDetailFactoryBean jobFactoryBean() {
JobDetailFactoryBean bean = new JobDetailFactoryBean();
bean.setDurability(true);
bean.setName("Sampler");
bean.setJobClass(SampleJob.class);
return bean;
}
@Bean
SimpleTriggerFactoryBean triggerFactoryBean(JobDetailFactoryBean jobFactoryBean) {
SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean();
bean.setName("Sampler Trigger");
bean.setRepeatInterval(20_000);
bean.setJobDetail(jobFactoryBean.getObject());
return bean;
}
@Bean
SchedulerFactoryBean schedulerFactoryBean(SimpleTriggerFactoryBean triggerFactoryBean, DataSource dataSource, Dependency dependency) {
Properties props = new Properties();
props.put("org.quartz.scheduler.instanceId", "AUTO");
props.put("org.quartz.jobStore.isClustered", "true");
SchedulerFactoryBean bean = new SchedulerFactoryBean();
bean.setTriggers(triggerFactoryBean.getObject());
bean.setSchedulerName("Demo Scheduler");
bean.setSchedulerContextAsMap(Collections.singletonMap("dependency", dependency));
bean.setOverwriteExistingJobs(true);
bean.setDataSource(dataSource);
bean.setQuartzProperties(props);
return bean;
}
这种情况在我们的工作服务器上经常发生,但在本地更难获得(可能是因为实际服务器是专用的并且比我的本地机器有更多的能力?)
要在任何机器上找到错误,以调试模式启动一个服务器并在 SimpleTriggerPersistenceDelegate.deleteExtendedTriggerProperties 上放置一个断点,然后在它执行后立即启动第二个服务器,你将得到这个异常
无论如何,在大约 40 次重新部署到我的本地集群 weblogic 服务器后,我在本地也遇到了这个错误。
问题在于默认情况下不使用事务管理器,因此不使用锁定。
解决问题需要调用schedulerFactoryBean的setTransactionManager方法