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方法