quartz在集羣環境下解決方案

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

你們想象一下,如今有  A    B    C3   臺機器同時做爲集羣服務器對外統一提供  SERVICE     web

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

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

那多線程裏就會存在「資源競爭」的問題,便可能產生髒讀,髒寫,因爲三臺  APP SERVER  裏都有  QUARTZ  ,所以會存在重複處理  TASK  的現象。  數據庫

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

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

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

本方案優勢:  架構

  1. 1.       每臺做爲集羣點的  APP SERVER  上均可以佈署  QUARTZ    oracle

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

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

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

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

解決方案:  

   

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

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

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

  1. 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  的集羣與自動錯誤冗餘。  

相關文章
相關標籤/搜索