Monday, September 4, 2017

Spring + Quartz : Scheduler Job with failure recovery

Most IT applications need scheduled processing e.g. maintenance, data flow, clean up etc. which are not initiated by user action. To handle this we can schedule jobs for a particular interval. But what if the job fails or server is down? We do not want to miss the data processing for the interval when job was down or failing. This article explains how to handle such scenario using a Spring and Quartz scheduler.

Problem Statement and Solution

We have a scheduler which processes data for last 10 minutes. But at times there are connectivity issues and server downtimes when the job would be down. We do not want to lose data for that interval. To conquer this we store job status and last execution time in database or property file. We read the last successful date in every run and start processing from that offset.

Maven Configuration

In this example, we will use Maven as the build tool so all you need to do is add the below dependencies to pom.xml.

pom.xml:
        <?xml version="1.0" encoding="UTF-8"?>
        <project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
        http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
               <groupId>org.javareferencegv.scheduler</groupId>
               <artifactId>springQuartzScheduler</artifactId>
               <version>0.0.1-SNAPSHOT</version>
               <packaging>jar</packaging>
               <name>springQuartzScheduler</name>
               <url>http://maven.apache.org</url>
               <properties>
                     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
               </properties>
               <dependencies>
                     <dependency>
                            <groupId>org.quartz-scheduler</groupId>
                            <artifactId>quartz</artifactId>
                            <version>2.2.1</version>
                     </dependency>
                     <dependency>
                            <groupId>org.springframework</groupId>
                            <artifactId>spring-context</artifactId>
                            <version>4.1.5.RELEASE</version>
                     </dependency>
                     <dependency>
                            <groupId>org.springframework</groupId>
                            <artifactId>spring-context-support</artifactId>
                            <version>4.1.5.RELEASE</version>
                     </dependency>
                     <dependency>
                            <groupId>org.springframework</groupId>
                            <artifactId>spring-tx</artifactId>
                            <version>4.1.5.RELEASE</version>
                     </dependency>
                     <dependency>
                            <groupId>org.slf4j</groupId>
                            <artifactId>slf4j-log4j12</artifactId>
                            <version>1.7.22</version>
                     </dependency>
               </dependencies>
        </project> 

 

Configuring Spring Application context

We would be configuring the below in spring app context to setup our scheduler :
  • SchedulerFactoryBean – FactoryBean that creates and configures a Quartz Scheduler, manages its lifecycle as part of the Spring application context, and exposes the Scheduler as bean reference for dependency injection.
  • SimpleTriggerFactoryBean – A Spring FactoryBean for creating a Quartz SimpleTrigger instance, supporting bean-style usage for trigger configuration.
    • jobDetail –  The job detail which would be triggered
    • startDelay –  The delay in starting job after the scheduler is started
    • repeatInterval – Interval in milis after which the job will be repeated
  • JobDetailFactoryBean – A Spring FactoryBean for creating a Quartz JobDetail instance, supporting bean-style usage for JobDetail configuration.
  • QuartzJobBean – Simple implementation of the Quartz Job interface. It will internally call executeInternal.

appContext.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    <!-- appContext file -->
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
           <bean id="demo-job"
                 class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
                 <property name="triggers">
                        <list>
                               <ref bean="demoTrigger" />
                        </list>
                 </property>
           </bean>
           <bean id="demoTrigger"
                  class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
                 <property name="jobDetail" ref="demoJobDetailBean" />
                 <property name="startDelay" value="600000" />
                 <property name="repeatInterval" value="600000" />
           </bean>
           <bean id="demoJobDetailBean"
                 class="org.springframework.scheduling.quartz.JobDetailFactoryBean"
                 scope="singleton">
                 <property name="jobClass"
                        value="org.javareferencegv.scheduler.springQuartzScheduler.DemoSchedular" />
           </bean>
    </beans>

 

Creating and Configuring the Job

We need to first create a Quartz job that defines the job to be executed. For that, we’ll subclass Spring’s QuartzJobBean and implement executeInternal() method where we will define the actions that we want the job to execute.

There are two unimplemented methods. The suggested implementation is mentioned in comments.
getLastRunDate()
updateJobStatus()

DemoScheduler.java:
    package org.javareferencegv.scheduler.springQuartzScheduler;
    import java.util.Date;
    import org.quartz.JobExecutionContext;
    import org.springframework.scheduling.quartz.QuartzJobBean;
    public class DemoSchedular extends QuartzJobBean {
           @Override
           protected void executeInternal(final JobExecutionContext context){
                 String status = "INCOMPLETE";
                 final Date jobRunDate = new Date();
                 final Date lastJobRunDate = null;
                 try {
                        /**
                         * Get the last date time when the job was successfully executed
                         * e.g. in PL/SQL it would be
                         * SELECT JOB_RUN_DATE FROM JOB_TRACKER WHERE JOB_STATUS='COMPLETE' ORDER BY JOB_RUN_DATE DESC;
                         */
                        // TODO: Implement this as per your db or property file
                        //lastJobRunDate = getLastRunDate();
                        /**
                         * Process all the data starting from last job run
                         */
                        System.out.println("Processing date from last successful execution - " + lastJobRunDate);
                        /**
                         * Mark status complete when the task is complete
                         */
                        status = "COMPLETE";
                 } catch (final Exception e) {
                        /**
                         * Something went wrong with the job mark the status as incomplete
                         * This ensures we process the backlog data next time
                         */
                        e.printStackTrace();
                        status = "INCOMPLETE";
                 }finally{
                        /**
                         * Update the job status in tracker
                         * e.g. in PL/SQL
                         * INSERT INTO JOB_TRACKER
                         * (STATUS,JOB_RUN_DATE)
                         * VALUE
                         * (status,jobStartDate)
                         */
                        // TODO: Implement this as per your db or property file
                        //updateJobStatus(status,jobRunDate);
                 }
           }
    }

 

Let’s execute

Here we create a sample main method and load the spring appcontext to run the scheduler.
App.java:
    package org.javareferencegv.scheduler.springQuartzScheduler;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    public class App
    {
           public static void main( final String[] args )
           {
                 System.out.println( "Starting demo scheduler" );
                 new ClassPathXmlApplicationContext("appContext.xml");
           }
    }

 

Thanks for reading. If anything can improve this article, your suggestions are most welcome.

No comments:

Post a Comment