前言:在上一篇文章(https://www.zifangsky.cn/844.html)中我介绍了“在Spring中集成Quartz的两种方式”。在这一篇文章中我将继续介绍基于Quartz的定时调度集群配置
(1)Quartz集群:
所谓Quartz集群,简单来说就是将使用了Quartz框架的项目部署到多个地位等价的服务器上(PS:每台服务器上部署了同一个基于Quartz的项目),这些项目节点之间的关系类似于master——slave。同一时间只有一个节点执行定时调度任务,当某一时刻这个执行任务的节点因为某种原因不能正常工作之后,其他的某个节点将会接过任务的执行权,继续执行定时调度任务。从而保证了整个集群的定时调度任务执行的可用性
那么现在问题来了,当某一个节点宕机之后,其他节点是如何知道的呢?或者说整个集群是如何保证了同一时间只有一个项目节点在执行任务?
其实这个问题的原因很简单,那就是Quartz框架使用了数据库来保存整个集群的状态,包括:有多少JobDetail、有多少Trigger、有哪些服务器节点以及它们的可用性状态、当前是哪个节点在执行任务等等。因此,当某个节点变得不可用时,其他节点就可以通过读取数据库的状态,然后选出一个可用的节点继续执行任务
下面我将以具体的实例来详细说明整个配置步骤:
(2)导入数据库文件:
从quartz-2.2.3/docs/dbTables 这个路径下选择当前使用的数据库对应的SQL文件,然后导入到数据库中
我这里测试是用的是MySQL,因此导入了“tables_mysql_innodb.sql”这个SQL文件
(3)定义一个测试任务TestJob.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package cn.zifangsky.job; import java.text.Format; import java.text.SimpleDateFormat; import java.util.Date; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; public class TestJob extends QuartzJobBean{ @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { Date current = new Date(); Format format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("--------| " + format.format(current) + " |---------"); } } |
(4)自定义“jobFactory”用于在Job中注入Spring管理的bean:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package cn.zifangsky.job; import org.quartz.spi.TriggerFiredBundle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.scheduling.quartz.AdaptableJobFactory; import org.springframework.stereotype.Component; @Component("autowiringBeanJobFactory") public class AutowiringBeanJobFactory extends AdaptableJobFactory { @Autowired private AutowireCapableBeanFactory autowiringBeanJobFactory; @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { //创建jobInstance Object jobInstance = super.createJobInstance(bundle); //向Spring中注入这个bean autowiringBeanJobFactory.autowireBean(jobInstance); return jobInstance; } } |
(5)Quartz相关配置文件context_quartz.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | <?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:jee="http://www.springframework.org/schema/jee" 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/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="cn.zifangsky.job" /> <!-- 配置线程池 --> <bean name="executor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="10" /> <property name="maxPoolSize" value="40" /> <property name="queueCapacity" value="25" /> <property name="keepAliveSeconds" value="30000" /> <property name="WaitForTasksToCompleteOnShutdown" value="true" /> </bean> <bean id="testJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <property name="jobClass" value="cn.zifangsky.job.TestJob"/> <!-- 如果一个job是非持久的,当没有活跃的trigger与之关联的时候,会被自动地从scheduler中删除 --> <property name="durability" value="true"/> <!-- 如果一个job是可恢复的,并且在其执行的时候,scheduler发生硬关闭,则当scheduler重新启动的时候,该job会被重新执行 --> <property name="requestsRecovery" value="false"/> </bean> <!-- 具体的Cron定时器 --> <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="testJobDetail" /> <property name="cronExpression" value="0/2 * * * * ?" /> <property name="timeZone" value="GMT+8:00" /> </bean> <bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <!-- 添加自定义的JobFactory --> <property name="jobFactory" ref="autowiringBeanJobFactory" /> <!-- 加载quartz配置文件 --> <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" /> <!--org.springframework.scheduling.quartz.SchedulerFactoryBean这个类中把spring上下 文以key/value的方式存放在了quartz的上下文中,然后可以用applicationContextSchedulerContextKey所定义的key得到对应的spring上下文--> <property name="applicationContextSchedulerContextKey" value="applicationContextKey"/> <property name="triggers"> <list> <ref bean="cronTrigger"/> </list> </property> <!-- <property name="jobDetails"> <list> <ref bean="testJobDetail"/> </list> </property> --> <property name="taskExecutor" ref="executor" /> </bean> </beans> |
关于这里的一些基本配置的含义可以自行参考我写的上篇文章,这里就不详细解释了。需要说明的是在这里引用的数据源、事务等配置都已经定义在Spring的配置文件中了。同时这里引用的“quartz.properties”文件是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #============================================================================ # Configure Main Scheduler Properties #============================================================================ org.quartz.scheduler.instanceName: TestScheduler org.quartz.scheduler.instanceId: AUTO org.quartz.scheduler.skipUpdateCheck: true 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: 12 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true #============================================================================ # Configure JobStore #============================================================================ 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.class: org.quartz.impl.jdbcjobstore.JobStoreCMT org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate |
注:关于这个配置可以参考quartz-2.2.3/examples/example10目录下的这个文件
(6)测试:
将这个测试项目导出成war包,然后分别放到两个不同端口的Tomcat上运行
i)可以发现只有一个节点在执行上面设置的控制台打印任务
ii)停掉这个执行任务的Tomcat,然后可以看到另一个Tomcat接过了执行权,继续执行控制台打印任务
到此,整个基于Quartz的定时调度集群就全部配置完成了