Preface
In order to ensure high availability and high concurrency of applications, multiple nodes are generally deployed; for timing tasks, if each node performs its own timing tasks, it consumes system resources on the one hand.
On the other hand, some tasks are executed multiple times, which may cause application logic problems, so a distributed scheduling system is needed to coordinate each node to perform timing tasks.
Spring integrates Quartz
Quartz is a mature task scheduling system. Spring is compatible with Quartz for easy development. Let’s see how to integrate it:
1.Maven dependency file
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.3.5.RELEASE</version> </dependency> <dependency> <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> <dependency> <groupId>org.quartz-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>
The main ones are Spring-related libraries, quartz libraries and mysql driver libraries. Note: Distributed scheduling requires database use, and mysql is selected here;
2. Configure job
There are two ways to configure jobs, namely: MethodInvokingJobDetailFactoryBean and JobDetailFactoryBean
2.1MethodInvokingJobDetailFactoryBean
When you want to call a method of a specific bean, the specific configuration is as follows:
<bean id="firstTask"> <property name="targetObject" ref="firstService" /> <property name="targetMethod" value="service" /> </bea>
2.2JobDetailFactoryBean
This method is more flexible and can set the passing parameters, as follows:
<bean id="firstTask"> <property name="jobClass" value="zh.maven.SQuartz.task.FirstTask" /> <property name="jobDataMap"> <map> <entry key="firstService" value-ref="firstService" /> </map> </property></bean>
The task class defined by jobClass inherits QuartzJobBean and implements the executeInternal method; jobDataMap is used to pass data to the job
3. Configure the triggers used for scheduling
Two trigger types are also provided: SimpleTriggerFactoryBean and CronTriggerFactoryBean
Focus on CronTriggerFactoryBean, this type is more flexible, as follows:
<bean id="firstCronTrigger"> <property name="jobDetail" ref="firstTask" /> <property name="cronExpression" value="0/5 * * ? * *" /> </bean>
jobDetail specifies the job configured in step 2, and the cronExpression is configured to execute the job every 5 seconds;
4. Configure the SchedulerFactoryBean of the Quartz scheduler
There are also two methods: memory RAMJobStore and database methods
4.1 Memory RAMJobStore
The job-related information is stored in memory, and each node stores its own, isolates each other, and is configured as follows:
<bean> <property name="triggers"> <list> <ref bean="firstCronTrigger" /> </list> </property> </bean>
4.2 Database method
The relevant information of the job is stored in the database. All nodes share the database. Each node communicates through the database to ensure that a job will only be executed on one node at the same time.
If a node fails, the job will be assigned to other nodes for execution. The specific configuration is as follows:
<bean id="dataSource" 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> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:quartz.properties" /> <property name="triggers"> <list> <ref bean="firstCronTrigger" /> </list> </property> </bean>
dataSource is used to configure data sources and data table related information. You can download the gz package from the official website. The sql file is under the path: docs/dbTables, which provides the sql file for the mainstream database;
The quartz.properties file configured in configLocation is in the org.quartz package of quartz.jar, which provides some default data, such as org.quartz.jobStore.class
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
Here you need to copy quartz.properties and make some modifications. The specific modifications are as follows:
org.quartz.scheduler.instanceId: AUTOorg.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTXorg.quartz.jobStore.isClustered: trueorg.quartz.jobStore.clusterCheckinInterval: 1000
5. Related categories
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; }}FirstTask inherits QuartzJobBean, implements executeInternal method, and calls FirstService
public class FirstService implements Serializable { private static final long serialVersionUID = 1L; public void service() { System.out.println(new SimpleDateFormat("YYYMMdd HH:mm:ss").format(new Date()) + "---start FirstService"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(new SimpleDateFormat("YYYMMdd HH:mm:ss").format(new Date()) + "---end FirstService"); }}FirstService needs to provide a serialization interface because it needs to be saved in the database;
public class App { public static void main(String[] args) { AbstractApplicationContext context = new ClassPathXmlApplicationContext("quartz.xml"); }}The main class is used to load the quartz configuration file;
Test 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 FirstService
Among them, A1 has log output, but A2 does not; when A1 is stopped, A2 has log output;
2. Add new jobs and create new ones: SecondTask and SecondService respectively. Add relevant configuration files at the same time. Start the App twice and observe the log:
The 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 FirstService
The A2 log is 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 SecondService
It can be found that both A1 and A2 have execution tasks, but the same task will only be executed on one node at the same time, and it is possible to be assigned to other nodes after the execution is completed;
3. If the interval time is less than the task execution time, for example, here is changed to sleep(6000)
The A1 log is as follows:
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 FirstService
The A2 log is 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 SecondService
The interval is 5 seconds, while the task execution takes 6 seconds. Observing the log, you can find that the task has not ended yet and a new task has begun. This situation may cause logical problems in the application, which is actually the question of whether the task can support serial;
4. @DisallowConcurrentExecution annotation ensures serialization of tasks
Add @DisallowConcurrentExecution annotation on FirstTask and SecondTask respectively, and the log results are as follows:
The 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 SecondService
The A2 log is as follows:
20180405 15:32:45---start SecondService
20180405 15:32:51---end SecondService
Observing the logs, you can find that the task will only start a new task after the end, and realize the serialization of the task;
Summarize
This article aims to have an intuitive understanding of Spring+Quartz distributed scheduling and solve the problem through actual use. Of course, there may be many questions such as how it is scheduled, what will happen if the database is hung, etc., and more in-depth understanding is needed.
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.