在叢集環境下,大家會碰到一直困擾的問題,即多個
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+
類反射的機制對原有程式作切面重構;
本人也事先搜尋了一些資料,發覺所有目前在
上或者在各大論壇裡提供的解決方案,要麼是隻解決了一部分,要麼是錯誤的,要麼是版本太老,要麼就是完全抄别人的。
尤其是在使用
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
政策的叢集大功告成,不用改原有代碼,配置一下我們就可作到
的叢集與自動錯誤備援。