【Quartz】Quartz儲存與持久化-基於quartz.properties的配置,quartz持久化
林炳文Evankaka原創作品。轉載請註明出處http://blog.csdn.net/evankaka
一、 Quartz儲存與持久化
Quartz提供兩種基本作業儲存類型。第一種類型叫做RAMJobStore,第二種類型叫做JDBC作業儲存。在預設情況下Quartz將任務調度的運行資訊儲存在記憶體中,這種方法提供了最佳的效能,因為記憶體中資料訪問最快。不足之處是缺乏資料的持久性,當程式路途停止或系統崩潰時,所有啟動並執行資訊都會丟失。
比如我們希望安排一個執行100次的任務,如果執行到50次時系統崩潰了,系統重啟時任務的執行計數器將從0開始。在大多數實際的應用中,我們往往並不需要儲存任務調度的現場資料,因為很少需要規劃一個指定執行次數的任務。對於僅執行一次的任務來說,其執行條件資訊本身應該是已經持久化的業務資料(如鎖定到期解鎖任務,解鎖的時間應該是業務資料),當執行完成後,條件資訊也會相應改變。當然調度現場資訊不僅僅是記錄運行次數,還包括調度規則、JobDataMap中的資料等等。
如果確實需要持久化任務調度資訊,Quartz允許你通過調整其屬性檔案,將這些資訊儲存到資料庫中。使用資料庫儲存任務調度資訊後,即使系統崩潰後重新啟動,任務的調度資訊將得到恢複。如前面所說的例子,執行50次崩潰後重新運行,計數器將從51開始計數。使用了資料庫儲存資訊的任務稱為持久化任務。
對比
類型 |
優點 |
缺點 |
RAMJobStore |
不要外部資料庫,配置容易,運行速度快 |
因為發送器資訊是儲存在被分配給JVM的記憶體裡面,所以,當應用程式停止運行時,所有調度資訊將被丟失。另外因為儲存到JVM記憶體裡面,所以可以儲存多少個Job和Trigger將會受到限制 |
JDBC作業儲存 |
支援叢集,因為所有的任務資訊都會儲存到資料庫中,可以控制事物,還有就是如果應用伺服器關閉或者重啟,任務資訊都不會丟失,並且可以恢複因伺服器關閉或者重啟而導致執行失敗的任務 |
運行速度的快慢取決與串連資料庫的快慢 |
二、Quartz儲存執行個體
下面開始說實現的步驟吧:
2.1、建立資料存放區表
因為需要把quartz的資料儲存到資料庫,所以要建立相關的資料庫。這個可以從下載到的quartz包裡面找到對應的sql指令碼,目前可以支援mysql,DB2,oracle等主流的資料庫,自己可以根據項目需要選擇合適的指令碼運行。
我的項目是mysql的,就在資料中建立了一個quartz的database,然後執行tables_mysql_innodb.sql指令碼建表。其中指令碼 檔案位於:E:\JarCom\quartz-2.2.1\docs\dbTables(根據你Quartz放置的目錄會不同)
然後開啟MySql的終端:
先建立一個資料庫,名為quartz;
然後執行指令碼:
運行結果:
首先會出現如下錯誤:
這裡有四張資料表建立失敗。
解決方案:
將tables_mysql_innodb.sql中的TYENGINEPE=InnoDB全部都替換成ENGINE=InnoDB;
再次執行:
表示資料庫表建立成功了
表建立好後可以看到相關的table
+————————–+
| Tables_in_quartz |
+————————–+
| QRTZ_BLOB_TRIGGERS |
| QRTZ_CALENDARS |
| QRTZ_CRON_TRIGGERS |
| QRTZ_FIRED_TRIGGERS |
| QRTZ_JOB_DETAILS |
| QRTZ_LOCKS |
| QRTZ_PAUSED_TRIGGER_GRPS |
| QRTZ_SCHEDULER_STATE |
| QRTZ_SIMPLE_TRIGGERS |
| QRTZ_SIMPROP_TRIGGERS |
| QRTZ_TRIGGERS |
+————————–+
2.2、建立工程並匯入包
建立一個java工程,匯入相關的jar包
這裡我就不多說了,可以到官網上去下載,本文使用的是最新的2,2.1
quartz:http://www.quartz-scheduler.org/
及mySql操作的包,和一個commons-lang包,整個工作最終目錄如下:
然後,需要在項目中加上對應的配置。2.3、首先是quartz.properties的配置
# Default Properties file for use by StdSchedulerFactory# to create a Quartz Scheduler Instance, if a different# properties file is not explicitly specified.# #叢集配置org.quartz.scheduler.instanceName: DefaultQuartzSchedulerorg.quartz.scheduler.rmi.export: falseorg.quartz.scheduler.rmi.proxy: falseorg.quartz.scheduler.wrapJobExecutionInUserTransaction: false org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPoolorg.quartz.threadPool.threadCount: 10org.quartz.threadPool.threadPriority: 5org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true org.quartz.jobStore.misfireThreshold: 60000 #============================================================================# Configure JobStore#============================================================================ #預設配置,資料儲存到記憶體#org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore#持久化配置org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTXorg.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegateorg.quartz.jobStore.useProperties:true#資料庫表首碼#org.quartz.jobStore.tablePrefix:qrtz_#org.quartz.jobStore.dataSource:qzDS #============================================================================# Configure Datasources#============================================================================#JDBC驅動#org.quartz.dataSource.qzDS.driver:com.mysql.jdbc.Driver#org.quartz.dataSource.qzDS.URL:jdbc:mysql://localhost:3306/quartz#org.quartz.dataSource.qzDS.user:root#org.quartz.dataSource.qzDS.password:christmas258@#org.quartz.dataSource.qzDS.maxConnection:10
其中org.quartz.jobStore.class是指明quartz的持久化用資料庫來儲存,
而org.quartz.jobStore.driverDelegateClass是根據選擇的資料庫類型不同而不同,我這裡的是mysql,所以是org.quartz.impl.jdbcjobstore.StdJDBCDelegate。
Quartz的屬性設定檔主要包括三方面的資訊:
1)叢集資訊;
2)調度器線程池;
3)任務調度現場資料的儲存。
·調度器屬性
第一部分有兩行,分別設定調度器的執行個體名(instanceName) 和執行個體 ID (instanceId)。屬性 org.quartz.scheduler.instanceName 可以是你喜歡的任何字串。它用來在用到多個調度器區分特定的調度器執行個體。多個調度器通常用在叢集環境中。現在的話,設定如下的一個字串就行:org.quartz.scheduler.instanceName :DefaultQuartzScheduler實際上,這也是當你沒有該屬性配置時的預設值。
調度器的第二個屬性是 org.quartz.scheduler.instanceId。和 instaneName 屬性一樣,instanceId 屬性也允許任何字串。這個值必須是在所有調度器執行個體中是唯一的,尤其是在一個叢集當中。假如你想 Quartz 幫你產生這個值的話,可以設定為 AUTO。如果 Quartz 架構是運行在非叢集環境中,那麼自動產生的值將會是 NON_CLUSTERED。假如是在叢集環境下使用 Quartz,這個值將會是主機名稱加上當前的日期和時間。大多情況下,設定為 AUTO 即可。
·線程池屬性
接下來的部分是設定有關線程必要的屬性值,這些線程在 Quartz 中是運行在後台擔當重任的。threadCount 屬性控制了多少個工作者線程被建立用來處理 Job。原則上是,要處理的 Job 越多,那麼需要的工作者線程也就越多。threadCount 的數值至少為 1。Quartz 沒有限定你設定工作者線程的最大值,但是在多數機器上設定該值超過100的話就會顯得相當不實用了,特別是在你的 Job 執行時間較長的情況下。這項沒有預設值,所以你必須為這個屬性設定一個值。
threadPriority 屬性設定工作者線程的優先順序。優先順序別高的線程比層級低的線程更優先得到執行。threadPriority 屬性的最大值是常量 java.lang.Thread.MAX_PRIORITY,等於10。最小值為常量 java.lang.Thread.MIN_PRIORITY,為1。這個屬性的正常值是 Thread.NORM_PRIORITY,為5。大多情況下,把它設定為5,這也是沒指定該屬性的預設值。
最後一個要設定的線程池屬性是 org.quartz.threadPool.class。這個值是一個實現了 org.quartz.spi.ThreadPool 介面的類的全限名稱。Quartz 內建的線程池實作類別是 org.quartz.smpl.SimpleThreadPool,它能夠滿足大多數使用者的需求。這個線程池實現具備簡單的行為,並經很好的測試過。它在調度器的生命週期中提供固定大小的線程池。你能根據需求建立自己的線程池實現,如果你想要一個隨需可伸縮的線程池時也許需要這麼做。這個屬性沒有預設值,你必須為其指定值。
·作業儲存設定
作業儲存部分的設定描述了在調度器執行個體的生命週期中,Job 和 Trigger 資訊是如何被儲存的。我們還沒有談論到作業儲存和它的目的;因為對當前例子是非必的,所以我們留待以後說明。現在的話,你所要瞭解的就是我們儲存調度器資訊在記憶體中而不是在關係型資料庫中就行了。
把調度器資訊儲存在記憶體中非常的快也易於配置。當調度器進程一旦被終止,所有的 Job 和 Trigger 的狀態就丟失了。要使 Job 儲存在記憶體中需通過設定 org.quartz.jobStrore.class 屬性為 org.quartz.simpl.RAMJobStore。假如我們不希望在 JVM 退出之後丟失調度器的狀態資訊的話,我們可以使用關係型資料庫來儲存這些資訊。這需要另一個作業儲存(JobStore) 實現,我們在後面將會討論到。第五章“Cron Trigger 和其他”和第六章“作業儲存和持久化”會提到你需要用到的不同類型的作業儲存實現。
2.4、建立Job類
package com.mucfc;import java.text.SimpleDateFormat;import java.util.Date;import org.apache.log4j.Logger;import org.quartz.Job;import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;public class MyJob implements Job{private static final Logger logger = Logger.getLogger(MyJob.class); @Overridepublic void execute(JobExecutionContext context)throws JobExecutionException {System.out.println("Hello quzrtz "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ").format(new Date()));}}
2.5、建立Job的執行類:
package com.mucfc;import java.text.ParseException;import java.util.List;import org.apache.commons.lang.StringUtils;import org.quartz.CronScheduleBuilder;import org.quartz.Job;import org.quartz.JobBuilder;import org.quartz.JobDetail;import org.quartz.JobKey;import org.quartz.Scheduler;import org.quartz.SchedulerException;import org.quartz.SchedulerFactory;import org.quartz.SimpleScheduleBuilder;import org.quartz.SimpleTrigger;import org.quartz.Trigger;import org.quartz.TriggerBuilder;import org.quartz.TriggerKey;import org.quartz.impl.StdSchedulerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class QuartzTest {private static SchedulerFactory sf = new StdSchedulerFactory();private static String JOB_GROUP_NAME = "ddlib";private static String TRIGGER_GROUP_NAME = "ddlibTrigger";public static void main(String[] args) throws SchedulerException,ParseException {startSchedule();//resumeJob();}/** * 開始一個simpleSchedule()調度 */public static void startSchedule() {try {// 1、建立一個JobDetail執行個體,指定QuartzJobDetail jobDetail = JobBuilder.newJob(MyJob.class)// 任務執行類.withIdentity("job1_1", "jGroup1")// 任務名,工作群組.build();// 2、建立TriggerSimpleScheduleBuilder builder = SimpleScheduleBuilder.simpleSchedule()// 設定執行次數 .repeatSecondlyForTotalCount(100);Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1_1", "tGroup1").startNow().withSchedule(builder).build();// 3、建立SchedulerScheduler scheduler = StdSchedulerFactory.getDefaultScheduler();scheduler.start();// 4、調度執行scheduler.scheduleJob(jobDetail, trigger);try {Thread.sleep(60000);} catch (InterruptedException e) {e.printStackTrace();}scheduler.shutdown();} catch (SchedulerException e) {e.printStackTrace();}}/** * 從資料庫中找到已經存在的job,並重新開戶調度 */public static void resumeJob() {try {SchedulerFactory schedulerFactory = new StdSchedulerFactory();Scheduler scheduler = schedulerFactory.getScheduler();// ①擷取調度器中所有的觸發器組List<String> triggerGroups = scheduler.getTriggerGroupNames();// ②重新恢複在tgroup1組中,名為trigger1_1觸發器的運行for (int i = 0; i < triggerGroups.size(); i++) {List<String> triggers = scheduler.getTriggerGroupNames();for (int j = 0; j < triggers.size(); j++) {Trigger tg = scheduler.getTrigger(new TriggerKey(triggers.get(j), triggerGroups.get(i)));// ②-1:根據名稱判斷if (tg instanceof SimpleTrigger&& tg.getDescription().equals("tgroup1.trigger1_1")) {// ②-1:恢複運行scheduler.resumeJob(new JobKey(triggers.get(j),triggerGroups.get(i)));}}}scheduler.start();} catch (Exception e) {e.printStackTrace();}}}
2.6、執行:
然後開啟資料庫,輸入
use quartz;select* from QRTZ_SIMPLE_TRIGGERS
然後把上面的程式停了,點擊右上方的紅框框
把主代碼改成:
public static void main(String[] args) throws SchedulerException,ParseException {//startSchedule();resumeJob();}
輸出結果:
同時,尋找資料中的表:
發現有變化 了。。TRIGGER_GROUP表示的執行的次數,TRIGGER_GROUP表示被激發的次數
來回的不斷停止程式再重新開始程式,結果如下。
重新停止再開啟後,TRIGGER_GROUP變為39,他會減去上一次運行中TRIGGER_GROUP的次數,TRIGGER_GROUP每次運行都會從0開始計算
然後等100次執行完後,這個調度就會從資料表中刪除:
結果如下:
這時,該表中的記錄已經變空。
值得注意的是,如果你使用JDBC儲存任務調度資料時,當你運行代碼
startSchedule();//resumeJob();
然後退出,當再次希望運行
startSchedule();//resumeJob();
時,系統將拋出JobDetail重名的異常:Unable to store Job with name: 'job1_1' and group: 'jGroup1', because one already exists with this identification.因為每次調用Scheduler#scheduleJob()時,Quartz都會將JobDetail和Trigger的資訊儲存到資料庫中,如果資料表中已經同名的JobDetail或Trigger,異常就產生了。
這裡再次運行應該這樣做:
//startSchedule();resumeJob();
三、一些說明
一些相關的意義:
QRTZ_CALENDARS 以 Blob 類型儲存 Quartz 的 Calendar 資訊 QRTZ_CRON_TRIGGERS 儲存 Cron Trigger,包括Cron運算式和時區資訊 QRTZ_FIRED_TRIGGERS 儲存與已觸發的 Trigger 相關的狀態資訊,以及相聯 Job的執行資訊QRTZ_PAUSED_TRIGGER_GRPS 儲存已暫停 Trigger組的資訊 QRTZ_SCHEDULER_STATE 儲存少量的有關 Scheduler 的狀態資訊,和別的Scheduler執行個體(假如是用於一個叢集中) QRTZ_LOCKS 儲存程式的悲觀鎖的資訊(假如使用了悲觀鎖) QRTZ_JOB_DETAILS 儲存每一個已配置的 Job 的詳細資料 QRTZ_JOB_LISTENERS 儲存有關已配置的 JobListener的資訊 QRTZ_SIMPLE_TRIGGERS儲存簡單的Trigger,包括重複次數,間隔,以及已觸的次數 QRTZ_BLOG_TRIGGERS Trigger 作為 Blob 類型儲存(用於 Quartz 使用者用JDBC建立他們自己定製的 Trigger 類型,JobStore並不知道如何儲存執行個體的時候) QRTZ_TRIGGER_LISTENERS 儲存已配置的 TriggerListener的資訊 QRTZ_TRIGGERS 儲存已配置的 Trigger 的資訊
--------------------------------------------------------------------------------------------------
quartz 持久化資料庫表格欄位解釋建表,SQL語句在dbTables檔案夾中可以找到,介紹下主要的幾張表:
表qrtz_job_details:儲存job詳細資料,該表需要使用者根據實際情況初始化 job_name:叢集中job的名字,該名字使用者自己可以隨意定製,無強行要求 job_group:叢集中job的所屬組的名字,該名字使用者自己隨意定製,無強行要求 job_class_name:叢集中個notejob實作類別的完全包名,quartz就是根據這個路徑到classpath找到該job類 is_durable:是否持久化,把該屬性設定為1,quartz會把job持久化到資料庫中 job_data:一個blob欄位,存放持久化job對象 表qrtz_triggers: 儲存trigger資訊 trigger_name:trigger的名字,該名字使用者自己可以隨意定製,無強行要求 trigger_group:trigger所屬組的名字,該名字使用者自己隨意定製,無強行要求 job_name:qrtz_job_details表job_name的外鍵 job_group:qrtz_job_details表job_group的外鍵 trigger_state:當前trigger狀態,設定為ACQUIRED,如果設定為WAITING,則job不會觸發 trigger_cron:觸發器類型,使用cron運算式 表qrtz_cron_triggers:儲存cron運算式表 trigger_name:qrtz_triggers表trigger_name的外鍵 trigger_group:qrtz_triggers表trigger_group的外鍵 cron_expression:cron運算式 表qrtz_scheduler_state:儲存叢集中note執行個體資訊,quartz會定時讀取該表的資訊判斷叢集中每個執行個體的目前狀態 instance_name:之前設定檔中org.quartz.scheduler.instanceId配置的名字,就會寫入該欄位,如果設定為AUTO,quartz會根據物理機名和目前時間產生一個名字 last_checkin_time:上次檢查時間 checkin_interval:檢查間隔時間
配置quartz.properties檔案:
#調度標識名 叢集中每一個執行個體都必須使用相同的名稱 org.quartz.scheduler.instanceName:scheduler#ID設定為自動擷取 每一個必須不同 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_#設定為TRUE不會出現序列化非字串類到 BLOB 時產生的類版本問題org.quartz.jobStore.useProperties : true#加入叢集 org.quartz.jobStore.isClustered : true#調度執行個體失效的檢查時間間隔 org.quartz.jobStore.clusterCheckinInterval:20000 #容許的最大作業延長時間 org.quartz.jobStore.misfireThreshold :60000#ThreadPool 實現的類名 org.quartz.threadPool.class:org.quartz.simpl.SimpleThreadPool#線程數量 org.quartz.threadPool.threadCount : 10#線程優先順序 org.quartz.threadPool.threadPriority : 5#自建立父線程org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true #設定資料來源org.quartz.dataSource.myXADS.jndiURL: CT#jbdi類名 org.quartz.dataSource.myXADS.java.naming.factory.initial :weblogic.jndi.WLInitialContextFactory#URLorg.quartz.dataSource.myXADS.java.naming.provider.url:=t3://localhost:7001【注】:在J2EE工程中如果想用資料庫管理Quartz的相關資訊,就一定要配置資料來源,這是Quartz的要求。
林炳文Evankaka原創作品。轉載請註明出處http://blog.csdn.net/evankaka