quartz在叢集環境下的最終解決方案
阿新 • • 發佈:2019-01-23
最近專案中使用了spring+Quartz定時任務、但是專案最近要叢集部署、多個APP下如何利用Quartz
協調處理任務。
大家可以思考一下、現在有 A、B、C三個應用同時作為叢集伺服器對外統一提供服務、每個應用下各有一個Quartz、它們會按照既定的時間自動執行各自的任務。我們先不說實現什麼功能,就說這樣的架構其實有點像多執行緒。那多執行緒裡就會存在“資源競爭”的問題,即可能產生髒讀,髒寫,由於三臺 應用 裡都有 Quartz,因此會存在重複處理 任務 的現象。
解決方案一:只在一臺 應用 上裝 Quartz,其它兩臺不裝,這樣叢集就沒有意義了。
解決方案二:使用其實Quartz自身可以例項化資料庫的特性就可以解決問題
本方案優點:
1. 每臺作為叢集點的 應用上都可以佈署 Quartz ;
2. Quartz 的 TASK ( 12 張表)例項化如資料庫,基於資料庫引擎及 High-Available 的策略(叢集的一種策略)自動協調每個節點的 QUARTZ ,當任一一節點的 QUARTZ 非正常關閉或出錯時,另幾個節點的 QUARTZ 會自動啟動;
3. 無需開發人員更改原已經實現的 QUARTZ ,使用 SPRING+ 類反射的機制對原有程式作切面重構;
解決方案:
1:去官網下載最新的 quartz 解壓 在目錄 docs\dbTables 下就會找到 tables_mysql.sql 檔案、建立資料庫Quartz 並匯入資料庫。
2:生成 quartz.properties 檔案,把它放在工程的 src 目錄下 修改配置檔案如下:
原因是在使用 quartz+spring 把 quartz 的 task 例項化進入資料庫時,會產生: serializable 的錯誤,原因在於:
這個 MethodInvokingJobDetailFactoryBean 類中的 methodInvoking 方法,是不支援序列化的,因此在把 QUARTZ 的 TASK 序列化進入資料庫時就會拋錯。網上有說把 SPRING 原始碼拿來,修改一下這個方案,然後再打包成 SPRING.jar 釋出,這些都是不好的方法,是不安全的。
必須根據 QuartzJobBean 來重寫一個自己的類 。
BootstrapJob.java: 引導Job,通過Spring容器獲取任務的Job,根據注入的targetJob,該Job必須實現Job2介面
4:配置 applicationContext-job.xml:
叢集環境下測試:
三個個節點都帶有 Quartz 任務,監控控制檯、此時只有一臺 quartz 在執行,另幾個節點上的 quartz 沒有執行。
此時手動 停掉那臺執行 QUARTZ 過了 10分鐘左右,另一個節點的 quartz 自動監測到了叢集中執行著的 quartz 的 instance 已經 停掉 ,因此 quartz 叢集會自動把任一臺可用的 APP上啟動起一個 quartz job 的任務。
至此 Quartz使用 叢集策略已經ok,不用改原有程式碼,配置一下我們就可做到 Quartz的叢集與自動錯誤冗餘。
所需jar包:quartz-all-1.6.6.jar spring.jar mysql-connector-java-3.1.11-bin.jar commons-pool-1.3.jar commons-logging-1.0.4.jar commons-dbcp-1.2.1.jar
大家可以思考一下、現在有 A、B、C三個應用同時作為叢集伺服器對外統一提供服務、每個應用下各有一個Quartz、它們會按照既定的時間自動執行各自的任務。我們先不說實現什麼功能,就說這樣的架構其實有點像多執行緒。那多執行緒裡就會存在“資源競爭”的問題,即可能產生髒讀,髒寫,由於三臺 應用 裡都有 Quartz,因此會存在重複處理 任務 的現象。
解決方案一:只在一臺 應用 上裝 Quartz,其它兩臺不裝,這樣叢集就沒有意義了。
解決方案二:使用其實Quartz自身可以例項化資料庫的特性就可以解決問題
本方案優點:
1. 每臺作為叢集點的 應用上都可以佈署 Quartz ;
2. Quartz 的 TASK ( 12 張表)例項化如資料庫,基於資料庫引擎及 High-Available 的策略(叢集的一種策略)自動協調每個節點的 QUARTZ ,當任一一節點的 QUARTZ 非正常關閉或出錯時,另幾個節點的 QUARTZ 會自動啟動;
3. 無需開發人員更改原已經實現的 QUARTZ ,使用 SPRING+ 類反射的機制對原有程式作切面重構;
解決方案:
1:去官網下載最新的 quartz 解壓 在目錄 docs\dbTables 下就會找到 tables_mysql.sql 檔案、建立資料庫Quartz 並匯入資料庫。
2:生成 quartz.properties 檔案,把它放在工程的 src 目錄下 修改配置檔案如下:
- #==============================================================
- #Configure Main Scheduler Properties
- #==============================================================
- org.quartz.scheduler.instanceName = quartzScheduler
- org.quartz.scheduler.instanceId = AUTO
- #==============================================================
- #Configure ThreadPool
- #==============================================================
- org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
- org.quartz.threadPool.threadCount = 10
- org.quartz.threadPool.threadPriority = 5
- org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
- #==============================================================
- #Configure JobStore
- #==============================================================
- 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.isClustered = true
- org.quartz.jobStore.clusterCheckinInterval = 20000
- org.quartz.jobStore.dataSource = myDS
- #==============================================================
- #Configure DataSource (此處填你自己的資料庫連線資訊)
- #==============================================================
- org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
- org.quartz.dataSource.myDS.URL = jdbc\:mysql\://localhost\:3306/quartz?useUnicode\=true&characterEncoding\=UTF-8
- org.quartz.dataSource.myDS.user = root
- org.quartz.dataSource.myDS.password = 123
- org.quartz.dataSource.myDS.maxConnections =30
原因是在使用 quartz+spring 把 quartz 的 task 例項化進入資料庫時,會產生: serializable 的錯誤,原因在於:
這個 MethodInvokingJobDetailFactoryBean 類中的 methodInvoking 方法,是不支援序列化的,因此在把 QUARTZ 的 TASK 序列化進入資料庫時就會拋錯。網上有說把 SPRING 原始碼拿來,修改一下這個方案,然後再打包成 SPRING.jar 釋出,這些都是不好的方法,是不安全的。
必須根據 QuartzJobBean 來重寫一個自己的類 。
BootstrapJob.java: 引導Job,通過Spring容器獲取任務的Job,根據注入的targetJob,該Job必須實現Job2介面
- /**
- * 引導Job,通過Spring容器獲取任務的Job,根據注入的targetJob,該Job必須實現Job2介面
- * @author zzp
- * @date 2014-7-7
- */
- public class BootstrapJob implements Serializable{
- private String targetJob ;
- public void executeInternal(ApplicationContext cxt) {
- Job2 job = (Job2)cxt.getBean(this.targetJob);
- job.executeInternal() ;
- }
- public String getTargetJob() {
- return targetJob;
- }
- public void setTargetJob(String targetJob) {
- this.targetJob = targetJob;
- }
- }
- /**
- * Quartz 與 Spring 整合時,自定義的Job可以擁有Spring的上下文,
- * 因此定義了該介面,自定義的Job需要實現該介面,並實現executeInternal的task,
- * 這樣解決了Quartz 與Spring 在叢集環境下,可以不需要序列化,
- * 只需要在executeInternal獲取Spring 上下文中的target job bean.
- * 呼叫其相關的處理函式,來處理任務
- * @author zzp
- * @date 2014-7-7
- */
- public interface Job2 extends Serializable{
- /**
- * 處理任務的核心函式
- *
- * @param cxt Spring 上下文
- */
- void executeInternal();
- }
- <p>public void execute(JobExecutionContext context) throws JobExecutionException
- {
- try
- {
- logger.debug("start");
- String targetClass = context.getMergedJobDataMap().getString("targetClass");
- //logger.debug("targetClass is "+targetClass);
- Class targetClassClass = null;
- if(targetClass!=null)
- {
- targetClassClass = Class.forName(targetClass); // Could throw ClassNotFoundException
- }
- Object targetObject = context.getMergedJobDataMap().get("targetObject");
- if(targetObject instanceof BootstrapJob){
- //Job2 job = (Job2)targetObject;
- //job.executeInternal(context.getScheduler().getContext().)
- ApplicationContext ac = (ApplicationContext)context.getScheduler().getContext().get("applicationContext");
- BootstrapJob target = (BootstrapJob)targetObject ;
- target.executeInternal(ac);
- }else{
- //logger.debug("targetObject is "+targetObject);
- String targetMethod = context.getMergedJobDataMap().getString("targetMethod");
- //logger.debug("targetMethod is "+targetMethod);
- String staticMethod = context.getMergedJobDataMap().getString("staticMethod");
- //logger.debug("staticMethod is "+staticMethod);
- Object[] arguments = (Object[])context.getMergedJobDataMap().get("arguments");
- //logger.debug("arguments are "+arguments);
- //logger.debug("creating MethodInvoker");
- MethodInvoker methodInvoker = new MethodInvoker();
- methodInvoker.setTargetClass(targetClassClass);
- methodInvoker.setTargetObject(targetObject);
- methodInvoker.setTargetMethod(targetMethod);
- methodInvoker.setStaticMethod(staticMethod);
- methodInvoker.setArguments(arguments);
- methodInvoker.prepare();
- //logger.info("Invoking: "+methodInvoker.getPreparedMethod().toGenericString());
- methodInvoker.invoke();
- }
- }
- catch(Exception e)
- {
- throw new JobExecutionException(e);
- }
- finally
- {
- logger.debug("end");
- }
- }
- }</p><p>
- </p>
- public class QuartzDeleteQueAction implements Job2 {
- private static final long serialVersionUID = 1L;
- private IQuesGroupService quesGroupService;
- public void executeInternal(){
- LogUtil.jobInfo("Quartz的任務排程執行刪除教研組試題html檔案開始");
- try {
- ServletContext context = ContextLoader.getCurrentWebApplicationContext().getServletContext();
- String pathHtml = context.getRealPath(Constants.PATH_HTML);
- //獲取被刪除試題No
- List<Object> list = quesGroupService.queryDeleteQues();
- for(Object obj:list){
- String quesName = pathHtml+"ques_"+obj.toString()+".html";
- FileUtil.delFile(quesName);//刪除無用html檔案
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- LogUtil.jobInfo("Quartz的任務排程執行刪除教研組試題html檔案結束");
- }
- public IQuesGroupService getQuesGroupService() {
- return quesGroupService;
- }
- public void setQuesGroupService(IQuesGroupService quesGroupService) {
- this.quesGroupService = quesGroupService;
- }
- }
4:配置 applicationContext-job.xml:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
- xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd" default-autowire="byName" default-lazy-init="true">
- <!-- 要呼叫的工作類 -->
- <bean id="quartzJob" class="com.web.action.QuartzDeleteQueAction"></bean>
- <!-- 引導Job -->
- <bean id="bootstrapJob" class="com.acts.web.quartz.BootstrapJob">
- <property name="targetJob" value="quartzJob" />
- </bean>
- <!-- 重寫方法 -->
- <bean id="jobTask" class="com.acts.web.quartz.MethodInvokingJobDetailFactoryBean">
- <property name="concurrent" value="true" />
- <property name="targetObject" ref="bootstrapJob" />
- </bean>
- <!-- 定義觸發時間 -->
- <bean id="doTime" class="org.springframework.scheduling.quartz.CronTriggerBean">
- <property name="jobDetail">
- <ref bean="jobTask"/>
- </property>
- <!-- cron表示式 -->
- <property name="cronExpression">
- <!--5點到20點 每10分鐘一次排程 -->
- <value>0 0/10 5-20 * * ?</value>
- </property>
- </bean>
- <!-- 總管理類 如果將lazy-init='false'那麼容器啟動就會執行排程程式 -->
- <bean id="startQuertz" lazy-init="false" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
- <property name="configLocation" value="classpath:quartz.properties" />
- <property name="dataSource" ref="dataSourceQuartz" />
- <property name="triggers">
- <list>
- <ref bean="doTime"/>
- </list>
- </property>
- <!-- 就是下面這句,因為該 bean 只能使用類反射來重構 -->
- <property name="applicationContextSchedulerContextKey" value="applicationContext" />
- </bean>
- </beans>
叢集環境下測試:
三個個節點都帶有 Quartz 任務,監控控制檯、此時只有一臺 quartz 在執行,另幾個節點上的 quartz 沒有執行。
此時手動 停掉那臺執行 QUARTZ 過了 10分鐘左右,另一個節點的 quartz 自動監測到了叢集中執行著的 quartz 的 instance 已經 停掉 ,因此 quartz 叢集會自動把任一臺可用的 APP上啟動起一個 quartz job 的任務。
至此 Quartz使用 叢集策略已經ok,不用改原有程式碼,配置一下我們就可做到 Quartz的叢集與自動錯誤冗餘。
所需jar包:quartz-all-1.6.6.jar spring.jar mysql-connector-java-3.1.11-bin.jar commons-pool-1.3.jar commons-logging-1.0.4.jar commons-dbcp-1.2.1.jar