Problem Background
My company is a fast-growing startup company with currently 200 people. Its main business is related to tourism and hotels. The application iteration and update cycle is relatively fast, so developers spend more Time to update = keep up with the pace of iterations, but lack of control over the entire system
Before there was a cluster, the company’s scheduled tasks were implemented
In the initial stage, the number of visits to the application was not So big, one server can fully satisfy the use. There are many scheduled tasks that need to be executed in the application
With the cluster, the company’s method of implementing scheduled tasks
As the number of users increases, the number of visits also increases As a result, one server cannot meet the high concurrency requirements, so the company deploys the application to the cluster, and the front end is through the nginx proxy (the application server IP may be isolated by a firewall, avoiding the direct use of IP + port + application name method of access).
In a cluster environment, the same scheduled task will be executed on every machine in the cluster. In this way, the scheduled task will be executed repeatedly, which will not only increase the burden on the server, but also cause additional overhead due to repeated execution of the scheduled task. Unpredictable errors, so the company's solution is to evenly distribute the tasks in the scheduled tasks to each machine in the cluster according to the number of clusters (the average score here refers to the previous scheduled task. Run on each machine, first artificially divide the task into several parts, and let all machines execute this person)
Defects in the current implementation of scheduled tasks in the cluster
Current company The way of processing scheduled tasks in the cluster is not a true distributed processing method, but a pseudo-distributed method (commonly known as the native method within the company). An obvious flaw of this method is that when the machine in the cluster goes down, the entire The scheduled task will hang up or cannot be run at once, which will have a serious impact on the business
Solution to the defect (the focus of this article)
Use spring+quartz to build a Set up a real distributed scheduled task system. After consulting relevant information, we learned that the quartz framework natively supports distributed scheduled tasks
Development IDE: Intellij IDEA
JDK version: 1.8
Spring version: 4.2.6
Quartz version: 2.2.1
Spring and Quartz integration configuration
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.aaron.clusterquartz.job"/> <bean name="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <!-- tomcat --> <!--<property name="jndiName" value="java:comp/env/jndi/mysql/quartz"/>--> <!-- jboss --> <property name="jndiName" value="jdbc/quartz"/> </bean> <!-- 分布式事务配置 start --> <!-- 配置线程池--> <bean name="executor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="15"/> <property name="maxPoolSize" value="25"/> <property name="queueCapacity" value="100"/> </bean> <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置调度任务--> <bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="configLocation" value="classpath:quartz.properties"/> <property name="dataSource" ref="dataSource"/> <property name="transactionManager" ref="transactionManager"/> <!-- 任务唯一的名称,将会持久化到数据库--> <property name="schedulerName" value="baseScheduler"/> <!-- 每台集群机器部署应用的时候会更新触发器--> <property name="overwriteExistingJobs" value="true"/> <property name="applicationContextSchedulerContextKey" value="appli"/> <property name="jobFactory"> <bean class="com.aaron.clusterquartz.autowired.AutowiringSpringBeanJobFactory"/> </property> <property name="triggers"> <list> <ref bean="printCurrentTimeScheduler"/> </list> </property> <property name="jobDetails"> <list> <ref bean="printCurrentTimeJobs"/> </list> </property> <property name="taskExecutor" ref="executor"/> </bean> <!-- 配置Job详情 --> <bean name="printCurrentTimeJobs" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <property name="jobClass" value="com.aaron.clusterquartz.job.PrintCurrentTimeJobs"/> <!--因为我使用了spring的注解,所以这里可以不用配置scheduler的属性--> <!--<property name="jobDataAsMap"> <map> <entry key="clusterQuartz" value="com.aaron.framework.clusterquartz.job.ClusterQuartz"/> </map> </property>--> <property name="durability" value="true"/> <property name="requestsRecovery" value="false"/> </bean> <!-- 配置触发时间 --> <bean name="printCurrentTimeScheduler" class="com.aaron.clusterquartz.cron.PersistableCronTriggerFactoryBean"> <property name="jobDetail" ref="printCurrentTimeJobs"/> <property name="cronExpression"> <value>0/10 * * * * ?</value> </property> <property name="timeZone"> <value>GMT+8:00</value> </property> </bean> <!-- 分布式事务配置 end --> </beans>
quartz property file
#============================================================================ # Configure JobStore # Using Spring datasource in quartzJobsConfig.xml # Spring uses LocalDataSourceJobStore extension of JobStoreCMT #============================================================================ org.quartz.jobStore.useProperties=true org.quartz.jobStore.tablePrefix = QRTZ_ org.quartz.jobStore.isClustered = true org.quartz.jobStore.clusterCheckinInterval = 5000 org.quartz.jobStore.misfireThreshold = 60000 org.quartz.jobStore.txIsolationLevelReadCommitted = true # Change this to match your DB vendor org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate #============================================================================ # Configure Main Scheduler Properties # Needed to manage cluster instances #============================================================================ org.quartz.scheduler.instanceId=AUTO org.quartz.scheduler.instanceName=MY_CLUSTERED_JOB_SCHEDULER org.quartz.scheduler.rmi.export = false org.quartz.scheduler.rmi.proxy = false #============================================================================ # Configure ThreadPool #============================================================================ org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 10 org.quartz.threadPool.threadPriority = 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
Related class description
AutowiringSpringBeanJobFactory class is to use spring annotations in the scheduler. If you do not use annotations, you can not apply this class and directly use
SpringBeanJobFactory
package com.aaron.clusterquartz.autowired; import org.quartz.spi.TriggerFiredBundle; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.scheduling.quartz.SpringBeanJobFactory; /** * @author * @description 使job类支持spring的自动注入 * @date 2016-05-27 */ public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware { private transient AutowireCapableBeanFactory beanFactory; public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { beanFactory = applicationContext.getAutowireCapableBeanFactory(); } @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { Object job = super.createJobInstance(bundle); beanFactory.autowireBean(job); return job; } }
package com.aaron.clusterquartz.job; import com.arron.util.DateUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.quartz.QuartzJobBean; import java.util.Date; /** * @author * @description 一句话描述该文件的用途 * @date 2016-05-23 */ public class PrintCurrentTimeJobs extends QuartzJobBean { private static final Log LOG_RECORD = LogFactory.getLog(PrintCurrentTimeJobs.class); //这里就是因为有上文中的AutowiringSpringBeanJobFactory才可以使用@Autowired注解,否则只能在配置文件中设置这属性的值 @Autowired private ClusterQuartz clusterQuartz; protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { LOG_RECORD.info("begin to execute task," + DateUtils.dateToString(new Date())); clusterQuartz.printUserInfo(); LOG_RECORD.info("end to execute task," + DateUtils.dateToString(new Date())); } }
for testing Result:
Since there is only one computer, I opened two ports 8080 and 8888 for testing. I set the above scheduled task to run every 10 seconds.
When I start port 8080, I can see that the console prints a statement every 10 seconds
In the comparison test of two ports starting at the same time It can be seen that only one port is running the scheduled task
After closing the port that is running the scheduled task, the other port that was not running before starts to take over and continues to run. Scheduled tasks
#At this point, we can clearly see that in a distributed scheduled task (or cluster), only one scheduled task will be running at the same time.
The above is the entire content of this article. I hope it will be helpful to everyone's learning. I also hope that everyone will support the PHP Chinese website.
For more articles related to the implementation of distributed timing task framework based on spring+quartz, please pay attention to the PHP Chinese website!