Quartz在Spring中叢集

hunhun1122發表於2018-04-27

概述

雖然單個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檔案

 

Xml程式碼  收藏程式碼
  1. jdbc.driverClassName=com.mysql.jdbc.Driver  
  2. jdbcjdbc.url=jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true  
  3. jdbc.username=root  
  4. jdbc.password=kfs  

 
2.配置applicationContext.xml檔案

 

 

Xml程式碼  收藏程式碼
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"  
  4.     xmlns:context="http://www.springframework.org/schema/context"  
  5.     xsi:schemaLocation="     
  6.     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
  7.    http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd  
  8.    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd  
  9.    ">  
  10.   
  11.     <context:component-scan base-package="com.sundoctor" />  
  12.   
  13.     <!-- 屬性檔案讀入 -->  
  14.     <bean id="propertyConfigurer"  
  15.         class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
  16.         <property name="locations">  
  17.             <list>  
  18.                 <value>classpath:jdbc.properties</value>  
  19.             </list>  
  20.         </property>  
  21.     </bean>  
  22.   
  23.   
  24.     <!-- 資料來源定義,使用c3p0 連線池 -->  
  25.     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  
  26.         destroy-method="close">  
  27.         <property name="driverClass" value="${jdbc.driverClassName}" />  
  28.         <property name="jdbcUrl" value="${jdbc.url}" />  
  29.         <property name="user" value="${jdbc.username}" />  
  30.         <property name="password" value="${jdbc.password}" />  
  31.         <property name="initialPoolSize" value="${cpool.minPoolSize}" />  
  32.         <property name="minPoolSize" value="${cpool.minPoolSize}" />  
  33.         <property name="maxPoolSize" value="${cpool.maxPoolSize}" />  
  34.         <property name="acquireIncrement" value="${cpool.acquireIncrement}" />  
  35.         <property name="maxIdleTime" value="${cpool.maxIdleTime}" />  
  36.     </bean>  
  37. </beans>  

 

 

 

建立Job測試服務類

Java程式碼  收藏程式碼
  1. package com.sundoctor.quartz.cluster.example;  
  2.   
  3. import java.io.Serializable;  
  4.   
  5. import org.slf4j.Logger;  
  6. import org.slf4j.LoggerFactory;  
  7. import org.springframework.stereotype.Service;  
  8.   
  9.   
  10. @Service("simpleService")  
  11. public class SimpleService {  
  12.       
  13.     private static final long serialVersionUID = 122323233244334343L;  
  14.     private static final Logger logger = LoggerFactory.getLogger(SimpleService.class);  
  15.       
  16.     public void testMethod1(){  
  17.         //這裡執行定時排程業務  
  18.         logger.info("testMethod1.......1");  
  19.     }  
  20.       
  21.     public void testMethod2(){  
  22.         logger.info("testMethod2.......2");   
  23.     }  
  24. }  

 

 

 

建立兩個Job類MyQuartzJobBean1、MyQuartzJobBean2

 

Java程式碼  收藏程式碼
  1. package com.sundoctor.quartz.cluster.example;  
  2.   
  3. import org.quartz.DisallowConcurrentExecution;  
  4. import org.quartz.JobExecutionContext;  
  5. import org.quartz.JobExecutionException;  
  6. import org.quartz.PersistJobDataAfterExecution;  
  7. import org.quartz.SchedulerException;  
  8. import org.slf4j.Logger;  
  9. import org.slf4j.LoggerFactory;  
  10. import org.springframework.context.ApplicationContext;  
  11. import org.springframework.scheduling.quartz.QuartzJobBean;  
  12.   
  13. @PersistJobDataAfterExecution  
  14. @DisallowConcurrentExecution// 不允許併發執行  
  15. public class MyQuartzJobBean1 extends QuartzJobBean {  
  16.   
  17.     private static final Logger logger = LoggerFactory.getLogger(MyQuartzJobBean1.class);  
  18.   
  19.     @Override  
  20.     protected void executeInternal(JobExecutionContext jobexecutioncontext) throws JobExecutionException {  
  21.   
  22.         SimpleService simpleService = getApplicationContext(jobexecutioncontext).getBean("simpleService",  
  23.                 SimpleService.class);  
  24.         simpleService.testMethod1();  
  25.   
  26.     }  
  27.   
  28.     private ApplicationContext getApplicationContext(final JobExecutionContext jobexecutioncontext) {  
  29.         try {  
  30.             return (ApplicationContext) jobexecutioncontext.getScheduler().getContext().get("applicationContextKey");  
  31.         } catch (SchedulerException e) {  
  32.             logger.error("jobexecutioncontext.getScheduler().getContext() error!", e);  
  33.             throw new RuntimeException(e);  
  34.         }  
  35.     }  
  36.   
  37. }  

 



配置 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程式碼  收藏程式碼
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">  
  5.   
  6.     <bean name="quartzScheduler"  
  7.         class="org.springframework.scheduling.quartz.SchedulerFactoryBean">  
  8.         <property name="dataSource">  
  9.             <ref bean="dataSource" />  
  10.         </property>  
  11.         <property name="applicationContextSchedulerContextKey" value="applicationContextKey" />  
  12.         <property name="configLocation" value="classpath:quartz.properties" />          
  13.         <property name="triggers">  
  14.             <list>  
  15.                 <ref bean="trigger1" />  
  16.                 <ref bean="trigger2" />  
  17.             </list>  
  18.         </property>  
  19.     </bean>  
  20.   
  21.     <bean id="jobDetail1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">  
  22.         <property name="jobClass">  
  23.             <value>com.sundoctor.quartz.cluster.example.MyQuartzJobBean1</value>  
  24.         </property>     
  25.         <property name="durability" value="true" />     
  26.         <property name="requestsRecovery" value="true" />       
  27.     </bean>  
  28.     <bean id="trigger1" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">  
  29.         <property name="jobDetail" ref="jobDetail1" />  
  30.         <property name="cronExpression" value="0/30 * * ? * * *" />  
  31.     </bean>  
  32.   
  33.     <bean id="jobDetail2" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">  
  34.         <property name="jobClass">  
  35.             <value>com.sundoctor.quartz.cluster.example.MyQuartzJobBean2</value>  
  36.         </property>     
  37.         <property name="durability" value="true" />     
  38.         <property name="requestsRecovery" value="true" />       
  39.     </bean>  
  40.     <bean id="trigger2" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">  
  41.         <property name="jobDetail" ref="jobDetail2" />  
  42.         <property name="cronExpression" value="0/10 * * ? * * *" />  
  43.     </bean>     
  44.   
  45. </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進行測試,在本例中只是簡單列印一下日誌。

Java程式碼  收藏程式碼
  1. package com.sundoctor.quartz.cluster.example.test;  
  2.   
  3. import org.springframework.context.ApplicationContext;  
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;  
  5.   
  6. public class MainTest {  
  7.   
  8.     /** 
  9.      * @param args 
  10.      */  
  11.     public static void main(String[] args) {  
  12.         ApplicationContext springContext = new ClassPathXmlApplicationContext(new String[]{"classpath:applicationContext.xml","classpath:applicationContext-quartz.xml"});  
  13.     }  
  14.   
  15. }  



Quartz 實際並不關心你是在相同的還是不同的機器上執行節點。當叢集是放置在不同的機器上時,通常稱之為水平叢集。節點是跑在同一臺機器是,稱之為垂直叢集。對於垂直叢集,存在著單點故障的問題。這對高可用性的應用來說是個壞訊息,因為一旦機器崩潰了,所有的節點也就被有效的終止了。

當你執行水平叢集時,時鐘應當要同步,以免出現離奇且不可預知的行為。假如時鐘沒能夠同步,Scheduler 例項將對其他節點的狀態產生混亂。有幾種簡單的方法來保證時鐘何持同步,而且也沒有理由不這麼做。最簡單的同步計算機時鐘的方式是使用某一個 Internet 時間伺服器(Internet Time Server ITS)。

沒什麼會阻止你在相同環境中使用叢集的和非叢集的 Quartz 應用。唯一要注意的是這兩個環境不要混用在相同的資料庫表。意思是非叢集環境不要使用與叢集應用相同的一套資料庫表;否則將得到希奇古怪的結果,叢集和非叢集的 Job 都會遇到問題。

假如你讓一個非叢集的 Quartz 應用與叢集節點並行著執行,設法使用 JobInitializationPlugin和 RAMJobStore。

相關文章