preface

To ensure high availability and concurrency of applications, multiple nodes are usually deployed. For scheduled tasks, if each node executes its own scheduled task, system resources are consumed. On the other hand, some tasks are executed multiple times, which may cause application logic problems. Therefore, a distributed scheduling system is required to coordinate the execution of scheduled tasks on each node.

Spring integration of Quartz

Quartz is a mature task scheduling system, which is compatible with Spring for easy development. Here is how to integrate:

1.Maven dependency files

<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> < version > 4.3.5. RELEASE < / version > < / dependency > < the dependency > < groupId > org. Springframework < / groupId > < artifactId > spring - the context - support < / artifactId > < version > 4.3.5. RELEASE < / version > < / dependency > < the dependency > < the groupId > org. Springframework < / groupId > < artifactId > spring - tx < / artifactId > < version > 4.3.5. RELEASE < / version > < / dependency >  <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> < version > 4.3.5. RELEASE < / version > < / dependency > < the dependency > < groupId > org. Quartz - the scheduler < / groupId > <artifactId> Quartz </artifactId> <version>2.2.3</version> </dependency> <dependency> <groupId>mysql</groupId> < artifactId > mysql connector - Java < / artifactId > < version > 5.1.29 < / version > < / dependency > < / dependencies >Copy the code

Note: Distributed scheduling needs to use database, mysql is selected here;

2. Configure the job

Provides two ways to configure the job, respectively is: MethodInvokingJobDetailFactoryBean and JobDetailFactoryBean

2.1 MethodInvokingJobDetailFactoryBean

To call a method on a particular bean, the configuration is as follows:

<bean id="firstTask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">  
    <property name="targetObject" ref="firstService" />  
    <property name="targetMethod" value="service" />  
</bea>Copy the code

2.2 JobDetailFactoryBean

This method is more flexible and can set the transfer parameters as follows:

<bean id="firstTask"
        class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="jobClass" value="zh.maven.SQuartz.task.FirstTask" />
        <property name="jobDataMap">
            <map>
                <entry key="firstService" value-ref="firstService" />
            </map>
        </property>
</bean>Copy the code

The task class defined by jobClass inherits QuartzJobBean and implements executeInternal method. JobDataMap is used to send data to jobs.

3. Configure the triggers for scheduling

There are also two types of trigger: SimpleTriggerFactoryBean and CronTriggerFactoryBean. CronTriggerFactoryBean

<bean id="firstCronTrigger"
    class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="firstTask" />
    <property name="cronExpression" value="0/5 * * ? * *" />
</bean>Copy the code

JobDetail specifies the job configured in Step 2. CronExpression is configured to execute the job every five seconds.

4. Configure the SchedulerFactoryBean of the Quartz scheduler

Two methods are also provided: RAMJobStore and database

4.1 memory RAMJobStore

Job information is stored in memory. Each node stores its own job information and is isolated from each other. The configuration is as follows:

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="firstCronTrigger" />
        </list>
    </property>
</bean>Copy the code

4.2 Database Mode

Job information is stored in a database shared by all nodes. Each node communicates with the database to ensure that a job is executed on only one node at a time. If a node fails, the job is assigned to another node.

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
        destroy-method="close">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/quartz" />
        <property name="user" value="root" />
        <property name="password" value="root" />
    </bean>
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:quartz.properties" />
        <property name="triggers">
            <list>
                <ref bean="firstCronTrigger" />
            </list>
        </property>
    </bean>Copy the code

The dataSource is used to configure the dataSource. You can download the gz package from the official website. The SQL file is in the path of docsdbTables. ConfigLocation configuration of quartz. The properties files in quartz. Jar org. Quartz package, which provides some default data, such as org. Quartz. JobStore. Class

org.quartz.jobStore.class: org.quartz.simpl.RAMJobStoreCopy the code

Here we need to copy quartz. Properties and make some changes as follows:

org.quartz.scheduler.instanceId: AUTO
org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.isClustered: true
org.quartz.jobStore.clusterCheckinInterval: 1000Copy the code

5. Related classes

public class FirstTask extends QuartzJobBean { private FirstService firstService; @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { firstService.service(); } public void setFirstService(FirstService firstService) { this.firstService = firstService; }}Copy the code

FirstTask inherits QuartzJobBean, implements executeInternal method, calls FirstService;

public class FirstService implements Serializable { private static final long serialVersionUID = 1L; public void service() { System.out.println(new SimpleDateFormat("YYYYMMdd HH:mm:ss").format(new Date()) + "---start FirstService"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(new SimpleDateFormat("YYYYMMdd HH:mm:ss").format(new Date()) + "---end FirstService"); }}Copy the code

FirstService needs to provide a serialization interface because it needs to be stored in a database;

public class App { public static void main(String[] args) { AbstractApplicationContext context = new ClassPathXmlApplicationContext("quartz.xml"); }}Copy the code

The main class is used to load the Quartz configuration file;

Testing distributed scheduling

1. Start the App twice at the same time and observe the log:

20180405 14:48:10---start FirstService
20180405 14:48:12---end FirstService
20180405 14:48:15---start FirstService
20180405 14:48:17---end FirstServiceCopy the code

A1 has logs, A2 does not. After A1 is disabled, A2 generates logs.

Create “SecondTask” and “SecondService” respectively, add related configuration files, and start App observation log: A1 log is as follows:

20180405 15:03:15---start FirstService
20180405 15:03:15---start SecondService
20180405 15:03:17---end FirstService
20180405 15:03:17---end SecondService
20180405 15:03:20---start FirstService
20180405 15:03:22---end FirstService
20180405 15:03:25---start FirstService
20180405 15:03:27---end FirstServiceCopy the code

A2 Logs are as follows:

20180405 15:03:20---start SecondService
20180405 15:03:22---end SecondService
20180405 15:03:25---start SecondService
20180405 15:03:27---end SecondServiceCopy the code

It can be found that A1 and A2 both execute tasks, but the same task can only be executed on one node at a time, and can be allocated to other nodes only after the execution is completed.

3. If the interval is shorter than the task execution time, for example, change it to sleep(6000)A1:

20180405 15:14:40---start FirstService
20180405 15:14:45---start FirstService
20180405 15:14:46---end FirstService
20180405 15:14:50---start FirstService
20180405 15:14:50---start SecondService
20180405 15:14:51---end FirstServiceCopy the code

A2 Logs are as follows:

20180405 15:14:40---start SecondService
20180405 15:14:45---start SecondService
20180405 15:14:46---end SecondService
20180405 15:14:51---end SecondServiceCopy the code

The interval is 5 seconds, but the task execution takes 6 seconds. By observing the log, you can find that the task has not finished yet and a new task has started. This situation may cause logic problems of the application, namely whether the task can support serial.

4. @ DisallowConcurrentExecution annotation tasks are serial In FirstTask and SecondTask respectively add @ DisallowConcurrentExecution annotations, log the results are as follows: A1 log is as follows:

20180405 15:32:45---start FirstService
20180405 15:32:51---end FirstService
20180405 15:32:51---start FirstService
20180405 15:32:51---start SecondService
20180405 15:32:57---end FirstService
20180405 15:32:57---end SecondService
20180405 15:32:57---start FirstService
20180405 15:32:57---start SecondServiceCopy the code

A2 Logs are as follows:

20180405 15:32:45---start SecondService
20180405 15:32:51---end SecondServiceCopy the code

By observing the log, it can be found that the task will start a new task only after the end, realizing the serialization of the task.

conclusion

This article aims to have an intuitive understanding of Spring+Quartz distributed scheduling, and solve the problems through practical use. Of course, there may be many questions, such as how it is scheduled, what will happen if the database fails, etc., but still need to do more in-depth understanding.