Quartz在Spring中集群
來自: http://sundoctor.iteye.com/blog/486055
概述
雖然單個Quartz實例能給予你很好的Job調度能力,但它不能滿足典型的企業需求,如可伸縮性、高可靠性滿足。假如你需要故障轉移的能力并能運行日益增多的 Job,Quartz集群勢必成為你應用的一部分了。使用 Quartz 的集群能力可以更好的支持你的業務需求,并且即使是其中一臺機器在最糟的時間崩潰了也能確保所有的 Job 得到執行。
Quartz 中集群如何工作
一個 Quartz 集群中的每個節點是一個獨立的 Quartz 應用,它又管理著其他的節點。意思是你必須對每個節點分別啟動或停止。不像許多應用服務器的集群,獨立的 Quartz 節點并不與另一其的節點或是管理節點通信。Quartz 應用是通過數據庫表來感知到另一應用的。
圖:表示了每個節點直接與數據庫通信,若離開數據庫將對其他節點一無所知
創建Quartz數據庫表
因為Quartz 集群依賴于數據庫,所以必須首先創建Quartz數據庫表。Quartz 包括了所有被支持的數據庫平臺的 SQL 腳本。在 <quartz_home>/docs/dbTables 目錄下找到那些 SQL 腳本,這里的 <quartz_home> 是解壓 Quartz 分發包后的目錄。
這里采用的Quartz 2.2.1版本,總共11張表,不同版本,表個數可能不同。數據庫為mysql,用tables_mysql_innodb.sql創建數據庫表。
配置數據庫連接池
1.配置jdbc.properties文件
jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true jdbc.username=root jdbc.password=kfs
2.配置applicationContext.xml文件
<?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:jdbc="http://www.springframework.org/schema/jdbc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <context:component-scan base-package="com.sundoctor" /> <!-- 屬性文件讀入 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:jdbc.properties</value> </list> </property> </bean> <!-- 數據源定義,使用c3p0 連接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driverClassName}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="initialPoolSize" value="${cpool.minPoolSize}" /> <property name="minPoolSize" value="${cpool.minPoolSize}" /> <property name="maxPoolSize" value="${cpool.maxPoolSize}" /> <property name="acquireIncrement" value="${cpool.acquireIncrement}" /> <property name="maxIdleTime" value="${cpool.maxIdleTime}" /> </bean> </beans>
創建Job測試服務類
package com.sundoctor.quartz.cluster.example; import java.io.Serializable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service("simpleService") public class SimpleService { private static final long serialVersionUID = 122323233244334343L; private static final Logger logger = LoggerFactory.getLogger(SimpleService.class); public void testMethod1(){ //這里執行定時調度業務 logger.info("testMethod1.......1"); } public void testMethod2(){ logger.info("testMethod2.......2"); } }
創建兩個Job類MyQuartzJobBean1、MyQuartzJobBean2
package com.sundoctor.quartz.cluster.example; import org.quartz.DisallowConcurrentExecution; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.PersistJobDataAfterExecution; import org.quartz.SchedulerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.scheduling.quartz.QuartzJobBean; @PersistJobDataAfterExecution @DisallowConcurrentExecution// 不允許并發執行 public class MyQuartzJobBean1 extends QuartzJobBean { private static final Logger logger = LoggerFactory.getLogger(MyQuartzJobBean1.class); @Override protected void executeInternal(JobExecutionContext jobexecutioncontext) throws JobExecutionException { SimpleService simpleService = getApplicationContext(jobexecutioncontext).getBean("simpleService", SimpleService.class); simpleService.testMethod1(); } private ApplicationContext getApplicationContext(final JobExecutionContext jobexecutioncontext) { try { return (ApplicationContext) jobexecutioncontext.getScheduler().getContext().get("applicationContextKey"); } catch (SchedulerException e) { logger.error("jobexecutioncontext.getScheduler().getContext() error!", e); throw new RuntimeException(e); } } }
配置 Quartz 使用集群
1.配置節點的 quartz.properties 文件
org.quartz.scheduler.instanceName = TestScheduler1
org.quartz.scheduler.instanceId = AUTO
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.maxMisfiresToHandleAtATime=10
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
org.quartz.scheduler.instanceName屬性可為任何值,用在 JDBC JobStore 中來唯一標識實例,但是所有集群節點中必須相同。
org.quartz.scheduler.instanceId 屬性為 AUTO即可,基于主機名和時間戳來產生實例 ID。
org.quartz.jobStore.class屬性為 JobStoreTX,將任務持久化到數據中。因為集群中節點依賴于數據庫來傳播 Scheduler 實例的狀態,你只能在使用 JDBC JobStore 時應用 Quartz 集群。這意味著你必須使用 JobStoreTX 或是 JobStoreCMT 作為 Job 存儲;你不能在集群中使用 RAMJobStore。
org.quartz.jobStore.isClustered 屬性為 true,你就告訴了 Scheduler 實例要它參與到一個集群當中。這一屬性會貫穿于調度框架的始終,用于修改集群環境中操作的默認行為。
org.quartz.jobStore.clusterCheckinInterval 屬性定義了Scheduler 實例檢入到數據庫中的頻率(單位:毫秒)。Scheduler 檢查是否其他的實例到了它們應當檢入的時候未檢入;這能指出一個失敗的 Scheduler 實例,且當前 Scheduler 會以此來接管任何執行失敗并可恢復的 Job。通過檢入操作,Scheduler 也會更新自身的狀態記錄。clusterChedkinInterval 越小,Scheduler 節點檢查失敗的 Scheduler 實例就越頻繁。默認值是 15000 (即15 秒)。
2.配置applicationContext-quartz.xml文件
<?xml version="1.0" encoding="UTF-8"?> <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 name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="dataSource"> <ref bean="dataSource" /> </property> <property name="applicationContextSchedulerContextKey" value="applicationContextKey" /> <property name="configLocation" value="classpath:quartz.properties" /> <property name="triggers"> <list> <ref bean="trigger1" /> <ref bean="trigger2" /> </list> </property> </bean> <bean id="jobDetail1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <property name="jobClass"> <value>com.sundoctor.quartz.cluster.example.MyQuartzJobBean1</value> </property> <property name="durability" value="true" /> <property name="requestsRecovery" value="true" /> </bean> <bean id="trigger1" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="jobDetail1" /> <property name="cronExpression" value="0/30 * * ? * * *" /> </bean> <bean id="jobDetail2" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <property name="jobClass"> <value>com.sundoctor.quartz.cluster.example.MyQuartzJobBean2</value> </property> <property name="durability" value="true" /> <property name="requestsRecovery" value="true" /> </bean> <bean id="trigger2" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="jobDetail2" /> <property name="cronExpression" value="0/10 * * ? * * *" /> </bean> </beans>
dataSource:項目中用到的數據源,里面包含了quartz用到的11張數據庫表;
applicationContextSchedulerContextKey: 是org.springframework.scheduling.quartz.SchedulerFactoryBean這個類中把spring上下 文以key/value的方式存放在了SchedulerContext中了,可以用applicationContextSchedulerContextKey所 定義的key得到對應spring 的ApplicationContext;
configLocation:用于指明quartz的配置文件的位置
requestsRecovery
requestsRecovery屬性必須設置為 true,當Quartz服務被中止后,再次啟動或集群中其他機器接手任務時會嘗試恢復執行之前未完成的所有任務。
運行Quartz集群
在相同或不同的機器上運行com.sundoctor.quartz.cluster.example.test.MainTest進行測試,在本例中只是簡單打印一下日志。
package com.sundoctor.quartz.cluster.example.test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MainTest { /** * @param args */ public static void main(String[] args) { ApplicationContext springContext = new ClassPathXmlApplicationContext(new String[]{"classpath:applicationContext.xml","classpath:applicationContext-quartz.xml"}); } }
Quartz 實際并不關心你是在相同的還是不同的機器上運行節點。當集群是放置在不同的機器上時,通常稱之為水平集群。節點是跑在同一臺機器是,稱之為垂直集群。對于垂直集群,存在著單點故障的問題。這對高可用性的應用來說是個壞消息,因為一旦機器崩潰了,所有的節點也就被有效的終止了。
當你運行水平集群時,時鐘應當要同步,以免出現離奇且不可預知的行為。假如時鐘沒能夠同步,Scheduler 實例將對其他節點的狀態產生混亂。有幾種簡單的方法來保證時鐘何持同步,而且也沒有理由不這么做。最簡單的同步計算機時鐘的方式是使用某一個 Internet 時間服務器(Internet Time Server ITS)。
沒什么會阻止你在相同環境中使用集群的和非集群的 Quartz 應用。唯一要注意的是這兩個環境不要混用在相同的數據庫表。意思是非集群環境不要使用與集群應用相同的一套數據庫表;否則將得到希奇古怪的結果,集群和非集群的 Job 都會遇到問題。
假如你讓一個非集群的 Quartz 應用與集群節點并行著運行,設法使用 JobInitializationPlugin和 RAMJobStore。