quartz在集羣環境下的最終解決方案

在集羣環境下,大家會碰到一直困擾的問題,即多個 APP 下如何用 quartz 協調處理自動化 JOB

大家想象一下,現在有 A B C3 臺機器同時作爲集羣服務器對外統一提供 SERVICE

A B C 3 臺機器上各有一個 QUARTZ ,他們會按照即定的 SCHEDULE 自動執行各自的任務。

我們先不說實現什麼功能,就說這樣的架構其實有點像多線程。

那多線程裏就會存在“資源競爭”的問題,即可能產生髒讀,髒寫,由於三臺 APP SERVER 裏都有 QUARTZ ,因此會存在重複處理 TASK 的現象。

一般外面的解決方案是隻在一臺 APP 上裝 QUARTZ ,其它兩臺不裝,這樣集羣就形同虛設了;

另一種解決方案是動代碼,這樣就要影響到原來已經寫好的 QUARTZ JOB 的代碼了,這對程序開發人員來說比較痛苦;

本人仔細看了一下 Spring 的結構和 QUARTZ 的文檔,結合 Quartz 自身可以實例化進數據的特性找到了相關的解決方案。

本方案優點:

1.       每臺作爲集羣點的 APP SERVER 上都可以佈署 QUARTZ

2.       QUARTZ TASK 12 張表)實例化如數據庫,基於數據庫引擎及 High-Available 的策略(集羣的一種策略)自動協調每個節點的 QUARTZ ,當任一一節點的 QUARTZ 非正常關閉或出錯時,另幾個節點的 QUARTZ 會自動啓動;

3.       無需開發人員更改原已經實現的 QUARTZ ,使用 SPRING+ 類反射的機制對原有程序作切面重構;

本人也事先搜索了一些資料,發覺所有目前在 GOOGLE 上或者在各大論壇裏提供的解決方案,要麼是隻解決了一部分,要麼是錯誤的,要麼是版本太老,要麼就是完全抄別人的。

尤其是在使用 QUARTZ+SPRING 對數據庫對象作實例化時會拋錯(源於 SPRING 的一個 BUG ),目前網上的解決方案全部是錯的或者乾脆沒說,本人在此方案中也會提出如何解決。

解決方案:

 

1.       QUARTZ TASK 實例化進數據庫, QUARTZ 只有實例化進入數據庫後才能做集羣,外面的解決方案說實例化在內存裏全部是錯的,把quartz-1.8.4/docs/dbTables/tables_oracle.sql ORACLE9I2 及以上版本中執行一下會生成 12 張表;

2.       生成 quartz.properties 文件,把它放在工程的 src 目錄下,使其能夠被編譯時納入 class path

一般我們的開發人員都喜歡使用 SPRING+QUARTZ ,因此這個 quartz.properties 都不用怎麼去寫,但是在集羣方案中 quartz.properties 必寫,如果不寫 quartz 會調用自身 jar 包中的 quartz.properties 作爲默認屬性文件,同時修改 quartz.xml 文件。

 

Quartz.xml 文件的內容 :

 

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd ">

<beans>

                <bean id="mapScheduler" lazy-init="false" autowire="no"

                                class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

                                <property name="configLocation" value="classpath:quartz.properties" />

                                <property name="triggers">

                                                <list>

                                                                <ref bean="cronTrigger" />

                                                </list>

                                </property>

                                <!— 就是下面這句,因爲該 bean 只能使用類反射來重構

                                <property name="applicationContextSchedulerContextKey" value="applicationContext" />          

</bean>

 

quartz.properties 文件的內容:

 

org.quartz.scheduler.instanceName = mapScheduler  

org.quartz.scheduler.instanceId = AUTO 

 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX 

 org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate 

 org.quartz.jobStore.dataSource = myXADS 

 org.quartz.jobStore.tablePrefix = QRTZ_ 

 org.quartz.jobStore.isClustered = true 

  

 org.quartz.dataSource.myXADS.jndiURL=jdbc/TestQuartzDS

 org.quartz.dataSource.myXADS.jndiAlwaysLookup = DB_JNDI_ALWAYS_LOOKUP 

 org.quartz.dataSource.myXADS.java.naming.factory.initial = weblogic.jndi.WLInitialContextFactory 

 org.quartz.dataSource.myXADS.java.naming.provider.url = t3://localhost:7020 

 org.quartz.dataSource.myXADS.java.naming.security.principal = weblogic 

 org.quartz.dataSource.myXADS.java.naming.security.credentials = weblogic 

3.       重寫 quartz QuartzJobBean

原因是在使用 quartz+spring quartz task 實例化進入數據庫時,會產生: serializable 的錯誤,原因在於:

<bean id="jobtask" class="org.springframework.scheduling.quartz. MethodInvokingJobDetailFactoryBean ">

                                <property name="targetObject">

                                                <ref bean="quartzJob"/>

                                </property>

                                <property name="targetMethod">

                                                <value>execute</value>

                                </property>

</bean>

這個 MethodInvokingJobDetailFactoryBean 類中的 methodInvoking 方法,是不支持序列化的,因此在把 QUARTZ TASK 序列化進入數據庫時就會拋錯。網上有說把 SPRING 源碼拿來,修改一下這個方案,然後再打包成 SPRING.jar 發佈,這些都是不好的方法,是不安全的。

必須根據 QuartzJobBean 來重寫一個自己的類,然後使用 SPRING 把這個重寫的類(我們就名命它爲: MyDetailQuartzJobBean )注入 appContext 中後,再使用 AOP 技術反射出原有的 quartzJobx( 就是開發人員原來已經做好的用於執行 QUARTZ JOB 的執行類 )

下面來看 MyDetailQuartzJobBean 類:

 

public class MyDetailQuartzJobBean extends QuartzJobBean {

                protected final Log logger = LogFactory.getLog(getClass());

 

                private String targetObject;

                private String targetMethod;

                private ApplicationContext ctx;

 

                protected void executeInternal(JobExecutionContext context)

                                                throws JobExecutionException {

                                try {

 

                                                logger.info("execute [" + targetObject + "] at once>>>>>>");

                                                Object otargetObject = ctx.getBean(targetObject);

                                                Method m = null;

                                                try {

                                                                m = otargetObject.getClass().getMethod(targetMethod,

                                                                                                new Class[] {});

 

                                                                m.invoke(otargetObject, new Object[] {});

                                                } catch (SecurityException e) {

                                                                logger.error(e);

                                                } catch (NoSuchMethodException e) {

                                                                logger.error(e);

                                                }

 

                                } catch (Exception e) {

                                                throw new JobExecutionException(e);

                                }

 

                }

 

                public void setApplicationContext(ApplicationContext applicationContext){

                                this.ctx=applicationContext;

                }

 

                public void setTargetObject(String targetObject) {

                                this.targetObject = targetObject;

                }

 

                public void setTargetMethod(String targetMethod) {

                                this.targetMethod = targetMethod;

                }

 

}

再來看完整的 quartz.xml (注意紅色加粗部分尤爲重要):

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd ">

<beans>

                <bean id="mapScheduler" lazy-init="false" autowire="no"

                                class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

                                <property name="configLocation" value="classpath:quartz.properties" />

                                <property name="triggers">

                                                <list>

                                                                <ref bean="cronTrigger" />

                                                </list>

                                </property>

                                <property name=" applicationContextSchedulerContextKey " value=" applicationContext " />

                               

                </bean>

               

 

 

                <bean id="quartzJob" class="com.testcompany.framework.quartz.QuartzJob">

                </bean>

 

                <bean id="jobTask" class="org.springframework.scheduling.quartz.JobDetailBean">

                                <property name="jobClass">

                                                <value>com.testcompany.framework.quartz. MyDetailQuartzJobBean </value>

                                </property>

                                <property name="jobDataAsMap">

                                                <map>

                                                                <entry key="quartzJob" value="quartzJob" />

                                                                <entry key="targetMethod" value="execute" />

                                                </map>

                                </property>

                </bean>

 

                <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">

                                <property name="jobDetail">

                                                <ref bean="jobTask" />

                                </property>

                                <property name="cronExpression">

                                                <value>0/5 * * * * ?</value>

                                </property>

                </bean>

</beans>

4.       下載最新的 quartz1.8 版,把 quartz-all-1.8.4.jar, quartz-oracle-1.8.4.jar,quartz-weblogic-1.8.4.jar 這三個包放到 web-inf/lib 目錄下,佈署。

 

 

測試:

 

幾個節點都帶有 quartz 任務,此時只有一臺 quartz 在運行,另幾個節點上的 quartz 沒有運行。

 

此時手動 shutdown 那臺運行 QUARTZ (在程序里加 system.out.println(“execute once…”), 運行 quartz 的那個節點在後臺會打印 execute once )的節點,過了 7 秒左右,另一個節點的 quartz 自動監測到了集羣中運行着的 quartz instance 已經 shutdown ,因此 quartz 集羣會自動把任一臺可用的 APP 上啓動起一個 quartz job 的任務。

 

自此, QUARTZ 使用 HA 策略的集羣大功告成,不用改原有代碼,配置一下我們就可作到 QUARTZ 的集羣與自動錯誤冗餘。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章