天天看點

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

在叢集環境下,大家會碰到一直困擾的問題,即多個

APP

下如何用

quartz

協調處理自動化

JOB

大家想象一下,現在有

A

B

C3

台機器同時作為叢集伺服器對外統一提供

SERVICE

C 3

台機器上各有一個

QUARTZ

,他們會按照即定的

SCHEDULE

自動執行各自的任務。

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

那多線程裡就會存在“資源競争”的問題,即可能産生髒讀,髒寫,由于三台

APP SERVER

裡都有

,是以會存在重複處理

TASK

的現象。

一般外面的解決方案是隻在一台

上裝

,其它兩台不裝,這樣叢集就形同虛設了;

另一種解決方案是動代碼,這樣就要影響到原來已經寫好的

QUARTZ JOB

的代碼了,這對程式開發人員來說比較痛苦;

本人仔細看了一下

Spring

的結構和

的文檔,結合

Quartz

自身可以執行個體化進資料的特性找到了相關的解決方案。

本方案優點:

1.      

每台作為叢集點的

上都可以布署

2.      

12

張表)執行個體化如資料庫,基于資料庫引擎及

High-Available

的政策(叢集的一種政策)自動協調每個節點的

,當任一一節點的

非正常關閉或出錯時,另幾個節點的

會自動啟動;

3.      

無需開發人員更改原已經實作的

,使用

SPRING+

類反射的機制對原有程式作切面重構;

本人也事先搜尋了一些資料,發覺所有目前在

GOOGLE

上或者在各大論壇裡提供的解決方案,要麼是隻解決了一部分,要麼是錯誤的,要麼是版本太老,要麼就是完全抄别人的。

尤其是在使用

QUARTZ+SPRING

對資料庫對象作執行個體化時會抛錯(源于

SPRING

的一個

BUG

),目前網上的解決方案全部是錯的或者幹脆沒說,本人在此方案中也會提出如何解決。

解決方案:

執行個體化進資料庫,

隻有執行個體化進入資料庫後才能做叢集,外面的解決方案說執行個體化在記憶體裡全部是錯的,把quartz-1.8.4/docs/dbTables/tables_oracle.sql

ORACLE9I2

及以上版本中執行一下會生成

張表;

生成

quartz.properties

檔案,把它放在工程的

src

目錄下,使其能夠被編譯時納入

class path

一般我們的開發人員都喜歡使用

SPRING+QUARTZ

,是以這個

都不用怎麼去寫,但是在叢集方案中

必寫,如果不寫

會調用自身

jar

包中的

作為預設屬性檔案,同時修改

quartz.xml

檔案。

Quartz.xml

檔案的内容

:

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

">

<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>

檔案的内容:

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 

重寫

QuartzJobBean

原因是在使用

quartz+spring

task

執行個體化進入資料庫時,會産生:

serializable

的錯誤,原因在于:

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

MethodInvokingJobDetailFactoryBean

                                <property name="targetObject">

                                                <ref bean="quartzJob"/>

                                <property name="targetMethod">

                                                <value>execute</value>

這個

類中的

methodInvoking

方法,是不支援序列化的,是以在把

序列化進入資料庫時就會抛錯。網上有說把

源碼拿來,修改一下這個方案,然後再打包成

SPRING.jar

釋出,這些都是不好的方法,是不安全的。

必須根據

來重寫一個自己的類,然後使用

把這個重寫的類(我們就名命它為:

MyDetailQuartzJobBean

)注入

appContext

中後,再使用

AOP

技術反射出原有的

quartzJobx(

就是開發人員原來已經做好的用于執行

的執行類

)

下面來看

類:

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) {

                                                }

                                } 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;

}

再來看完整的

(注意紅色加粗部分尤為重要):

                                <property name="

applicationContextSchedulerContextKey

" value="

applicationContext

" />

                </bean>

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

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

                                <property name="jobClass">

                                                <value>com.testcompany.framework.quartz.

</value>

                                <property name="jobDataAsMap">

                                                <map>

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

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

                                                </map>

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

                                <property name="jobDetail">

                                                <ref bean="jobTask" />

                                <property name="cronExpression">

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

</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

目錄下,布署。

測試:

幾個節點都帶有

任務,此時隻有一台

在運作,另幾個節點上的

沒有運作。

此時手動

shutdown

那台運作

(在程式裡加

system.out.println(“execute once…”),

運作

的那個節點在背景會列印

execute once

)的節點,過了

7

秒左右,另一個節點的

自動監測到了叢集中運作着的

instance

已經

,是以

叢集會自動把任一台可用的

上啟動起一個

quartz job

的任務。

自此,

使用

HA

政策的叢集大功告成,不用改原有代碼,配置一下我們就可作到

的叢集與自動錯誤備援。