Configuring Quartz Scheduler to Run in Clustered Environment

The goal of running a Quartz job in the clustered environment is NOT to have duplicate running jobs. The triggered job should run just one time regardless of the number of nodes in the clustered environment.

  1. Download Quartz and extract the file.
  2. Navigate to quartz-x.x.x -> docs -> dbTables and run the database SQL script to create the Quartz tables.

    quartz-x.x.x
    |- docs
       |- dbTables
          |- tables_<database>.sql <- Pick one that matches your database
       |- images
    |- examples
    |- javadoc
    |- lib
    |- licenses
    |- src
    

  3. Add the Quartz dependency in pom.xml:-

    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.2.0</version>
    </dependency>
    

  4. Create quartz.properties file under src/main/resources with the following configuration:-

    #============================================================================
    # Configure Main Scheduler Properties
    #============================================================================
    
    org.quartz.scheduler.instanceId = AUTO
    org.quartz.scheduler.makeSchedulerThreadDaemon = true
    
    #============================================================================
    # Configure ThreadPool
    #============================================================================
    
    org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
    org.quartz.threadPool.threadCount = 1
    org.quartz.threadPool.makeThreadsDaemons = true
    
    #============================================================================
    # Configure JobStore
    #============================================================================
    
    org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.MSSQLDelegate
    org.quartz.jobStore.isClustered = true
    

    Remember to change org.quartz.jobStore.driverDelegateClass value to match your database type. In my case, I’m using MS SQL Server.

  5. I want to be able to autowire Spring beans in my Quartz job. So, I created a custom job factory called AutowiringSpringBeanJobFactory:-

    public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory 
        implements ApplicationContextAware {
    
        private transient AutowireCapableBeanFactory beanFactory;
    
        @Override
        public void setApplicationContext(final ApplicationContext context) {
            beanFactory = context.getAutowireCapableBeanFactory();
        }
    
        @Override
        protected Object createJobInstance(final TriggerFiredBundle bundle) 
            throws Exception {
            final Object job = super.createJobInstance(bundle);
            beanFactory.autowireBean(job);
            return job;
        }
    }
    

  6. In my Quartz job, I can reuse my existing Spring bean by autowiring it:-

    @Service
    @DisallowConcurrentExecution
    public class MyJob implements Job {
        @Autowired
        private MyService myService;
    
        @Override
        public void execute(JobExecutionContext jobExecutionContext) 
            throws JobExecutionException {
            System.out.println("Message: " + myService.getHelloWorld());
        }
    }
    

  7. In this example, I’m creating a Java-based Spring configuration called QuartzConfig:-

    @Configuration
    public class QuartzConfig {
    
        // this data source points to the database that contains Quartz tables
        @Autowired
        private DataSource dataSource;
    
        @Autowired
        private PlatformTransactionManager transactionManager;
    
        @Autowired
        private ApplicationContext applicationContext;
    
        @Bean
        public SchedulerFactoryBean quartzScheduler() {
            SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean();
    
            quartzScheduler.setQuartzProperties(quartzProperties());
            quartzScheduler.setDataSource(dataSource);
            quartzScheduler.setTransactionManager(transactionManager);
            quartzScheduler.setOverwriteExistingJobs(true);
    
            // Custom job factory of spring with DI support for @Autowired
            AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
            jobFactory.setApplicationContext(applicationContext);
            quartzScheduler.setJobFactory(jobFactory);
    
            Trigger[] triggers = {
                    processMyJobTrigger().getObject()
            };
    
            quartzScheduler.setTriggers(triggers);
    
            return quartzScheduler;
        }
    
        @Bean
        public JobDetailFactoryBean processMyJob() {
            JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
            jobDetailFactory.setJobClass(MyJob.class);
            jobDetailFactory.setDurability(true);
            return jobDetailFactory;
        }
    
        @Bean
        // Configure cron to fire trigger every 1 minute
        public CronTriggerFactoryBean processMyJobTrigger() {
            CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
            cronTriggerFactoryBean.setJobDetail(processMyJob().getObject());
            cronTriggerFactoryBean.setCronExpression("0 0/1 * * * ?");
            return cronTriggerFactoryBean;
        }
    
        @Bean
        public Properties quartzProperties() {
            PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
            propertiesFactoryBean.setLocation(new ClassPathResource("quartz.properties"));
            Properties properties;
    
            try {
                propertiesFactoryBean.afterPropertiesSet();
                properties = propertiesFactoryBean.getObject();
            }
            catch (IOException e) {
                throw new RuntimeException("Unable to load quartz.properties", e);
            }
    
            return properties;
        }
    }
    

Advertisements

3 thoughts on “Configuring Quartz Scheduler to Run in Clustered Environment

    1. Choon-Chern Lim Post author

      The database manages the semaphore to ensure only one running node is executing the triggerred job. If you run this SQL query select * from dbo.QRTZ_TRIGGERS, you will see which job is currently running, when it will run again, etc.

      Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s