Quartz API 採用多面方式在 Java 應用程序中進行任務調度 php
Quartz 是個開放源碼項目,提供了豐富的做業調度集。在這篇文章中,軟件工程師 Michael Lipton 和 IT 架構師 Soobaek Jang 對 Quartz API 進行了介紹,從對框架的通常概述開始,並以一系列展現 Quart 基本特性的代碼示例做爲結束。在閱讀完本文並看過代碼示例後,您應當可以把 Quartz 的基本特性應用到任何 Java™ 應用程序中。 html
Quartz 是個開源的做業調度框架,爲在 Java 應用程序中進行做業調度提供了簡單卻強大的機制。Quartz 容許開發人員根據時間間隔(或天)來調度做業。它實現了做業和觸發器的多對多關係,還能把多個做業與不一樣的觸發器關聯。整合了 Quartz 的應用程序能夠重用來自不一樣事件的做業,還能夠爲一個事件組合多個做業。雖然能夠經過屬性文件(在屬性文件中能夠指定 JDBC 事務的數據源、全局做業和/或觸發器偵聽器、插件、線程池,以及更多)配置 Quartz,但它根本沒有與應用程序服務器的上下文或引用集成在一塊兒。結果就是做業不能訪問 Web 服務器的內部函數;例如,在使用 WebSphere 應用服務器時,由 Quartz 調度的做業並不能影響服務器的動態緩存和數據源。 java
本文使用一系列代碼示例介紹 Quartz API,演示它的機制,例如做業、觸發器、做業倉庫和屬性。 數據庫
要開始使用 Quartz,須要用 Quartz API 對項目進行配置。步驟以下: 編程
爲了方便讀者,我已經把全部必要的文件,包括 DB2 JDBC 文件,編譯到一個 zip 文件中。請參閱 下載 小節下載代碼。 緩存
如今來看一下 Quartz API 的主要組件。 服務器
Quartz 調度包的兩個基本單元是做業和觸發器。做業 是可以調度的可執行任務,觸發器 提供了對做業的調度。雖然這兩個實體很容易合在一塊兒,但在 Quartz 中將它們分離開來是有緣由的,並且也頗有益處。 架構
經過把要執行的工做與它的調度分開,Quartz 容許在不丟失做業自己或做業的上下文的狀況下,修改調度觸發器。並且,任何單個的做業均可以有多個觸發器與其關聯。 app
經過實現 org.quartz.job 接口,可使 Java 類變成可執行的。清單 1 提供了 Quartz 做業的一個示例。這個類用一條很是簡單的輸出語句覆蓋了 execute(JobExecutionContext context) 方法。這個方法能夠包含咱們想要執行的任何代碼(全部的代碼示例都基於 Quartz 1.5.2,它是編寫這篇文章時的穩定發行版)。 框架
package com.ibm.developerworks.quartz; import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class SimpleQuartzJob implements Job { public SimpleQuartzJob() { } public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("In SimpleQuartzJob - executing its JOB at " + new Date() + " by " + context.getTrigger().getName()); } }
請注意,execute 方法接受一個 JobExecutionContext 對象做爲參數。這個對象提供了做業實例的運行時上下文。特別地,它提供了對調度器和觸發器的訪問,這二者協做來啓動做業以及做業的 JobDetail 對象的執行。Quartz 經過把做業的狀態放在 JobDetail 對象中並讓JobDetail 構造函數啓動一個做業的實例,分離了做業的執行和做業周圍的狀態。JobDetail 對象儲存做業的偵聽器、羣組、數據映射、描述以及做業的其餘屬性。
觸發器能夠實現對任務執行的調度。Quartz 提供了幾種不一樣的觸發器,複雜程度各不相同。清單 2 中的 SimpleTrigger 展現了觸發器的基礎:
public void task() throws SchedulerException { // Initiate a Schedule Factory SchedulerFactory schedulerFactory = new StdSchedulerFactory(); // Retrieve a scheduler from schedule factory Scheduler scheduler = schedulerFactory.getScheduler(); // current time long ctime = System.currentTimeMillis(); // Initiate JobDetail with job name, job group, and executable job class JobDetail jobDetail = new JobDetail("jobDetail-s1", "jobDetailGroup-s1", SimpleQuartzJob.class); // Initiate SimpleTrigger with its name and group name SimpleTrigger simpleTrigger = new SimpleTrigger("simpleTrigger", "triggerGroup-s1"); // set its start up time simpleTrigger.setStartTime(new Date(ctime)); // set the interval, how often the job should run (10 seconds here) simpleTrigger.setRepeatInterval(10000); // set the number of execution of this job, set to 10 times. // It will run 10 time and exhaust. simpleTrigger.setRepeatCount(100); // set the ending time of this job. // We set it for 60 seconds from its startup time here // Even if we set its repeat count to 10, // this will stop its process after 6 repeats as it gets it endtime by then. //simpleTrigger.setEndTime(new Date(ctime + 60000L)); // set priority of trigger. If not set, the default is 5 //simpleTrigger.setPriority(10); // schedule a job with JobDetail and Trigger scheduler.scheduleJob(jobDetail, simpleTrigger); // start the scheduler scheduler.start(); }
清單 2 開始時實例化一個 SchedulerFactory,得到此調度器。就像前面討論過的,建立 JobDetail 對象時,它的構造函數要接受一個 Job 做爲參數。顧名思義,SimpleTrigger 實例至關原始。在建立對象以後,設置幾個基本屬性以當即調度任務,而後每 10 秒重複一次,直到做業被執行 100 次。
還有其餘許多方式能夠操縱 SimpleTrigger。除了指定重複次數和重複間隔,還能夠指定做業在特定日曆時間執行,只需給定執行的最長時間或者優先級(稍後討論)。執行的最長時間能夠覆蓋指定的重複次數,從而確保做業的運行不會超過最長時間。
CronTrigger 支持比 SimpleTrigger 更具體的調度,並且也不是很複雜。基於 cron 表達式,CronTrigger 支持相似日曆的重複間隔,而不是單一的時間間隔 —— 這相對 SimpleTrigger 而言是一大改進。
Cron 表達式包括如下 7 個字段:
Cron 觸發器利用一系列特殊字符,以下所示:
全部這些定義看起來可能有些嚇人,可是隻要幾分鐘練習以後,cron 表達式就會顯得十分簡單。
清單 3 顯示了 CronTrigger 的一個示例。請注意 SchedulerFactory、Scheduler 和 JobDetail 的實例化,與 SimpleTrigger 示例中的實例化是相同的。在這個示例中,只是修改了觸發器。這裏指定的 cron 表達式(「0/5 * * * * ?」)安排任務每 5 秒執行一次。
public void task() throws SchedulerException { // Initiate a Schedule Factory SchedulerFactory schedulerFactory = new StdSchedulerFactory(); // Retrieve a scheduler from schedule factory Scheduler scheduler = schedulerFactory.getScheduler(); // current time long ctime = System.currentTimeMillis(); // Initiate JobDetail with job name, job group, and executable job class JobDetail jobDetail = new JobDetail("jobDetail2", "jobDetailGroup2", SimpleQuartzJob.class); // Initiate CronTrigger with its name and group name CronTrigger cronTrigger = new CronTrigger("cronTrigger", "triggerGroup2"); try { // setup CronExpression CronExpression cexp = new CronExpression("0/5 * * * * ?"); // Assign the CronExpression to CronTrigger cronTrigger.setCronExpression(cexp); } catch (Exception e) { e.printStackTrace(); } // schedule a job with JobDetail and Trigger scheduler.scheduleJob(jobDetail, cronTrigger); // start the scheduler scheduler.start(); }
如上所示,只用做業和觸發器,就能訪問大量的功能。可是,Quartz 是個豐富而靈活的調度包,對於願意研究它的人來講,它還提供了更多功能。下一節討論 Quartz 的一些高級特性。
Quartz 提供了兩種不一樣的方式用來把與做業和觸發器有關的數據保存在內存或數據庫中。第一種方式是 RAMJobStore 類的實例,這是默認設置。這個做業倉庫最易使用,並且提供了最佳性能,由於全部數據都保存在內存中。這個方法的主要不足是缺少數據的持久性。由於數據保存在 RAM 中,因此應用程序或系統崩潰時,全部信息都會丟失。
爲了修正這個問題,Quartz 提供了 JDBCJobStore。顧名思義,做業倉庫經過 JDBC 把全部數據放在數據庫中。數據持久性的代價就是性能下降和複雜性的提升。
在前面的示例中,已經看到了 RAMJobStore 實例的工做狀況。由於它是默認的做業倉庫,因此顯然不須要額外設置就能使用它。可是,使用JDBCJobStore 須要一些初始化。
在應用程序中設置使用 JDBCJobStore 須要兩步:首先必須建立做業倉庫使用的數據庫表。 JDBCJobStore 與全部主流數據庫都兼容,並且 Quartz 提供了一系列建立表的 SQL 腳本,可以簡化設置過程。能夠在 Quartz 發行包的 「docs/dbTables」目錄中找到建立表的 SQL 腳本。第二,必須定義一些屬性,如表 1 所示:
屬性名稱 | 值 |
---|---|
org.quartz.jobStore.class | org.quartz.impl.jdbcjobstore.JobStoreTX (or JobStoreCMT) |
org.quartz.jobStore.tablePrefix | QRTZ_ (optional, customizable) |
org.quartz.jobStore.driverDelegateClass | org.quartz.impl.jdbcjobstore.StdJDBCDelegate |
org.quartz.jobStore.dataSource | qzDS (customizable) |
org.quartz.dataSource.qzDS.driver | com.ibm.db2.jcc.DB2Driver (could be any other database driver) |
org.quartz.dataSource.qzDS.url | jdbc:db2://localhost:50000/QZ_SMPL (customizable) |
org.quartz.dataSource.qzDS.user | db2inst1 (place userid for your own db) |
org.quartz.dataSource.qzDS.password | pass4dbadmin (place your own password for user) |
org.quartz.dataSource.qzDS.maxConnections | 30 |
清單 4 展現了 JDBCJobStore 提供的數據持久性。就像在前面的示例中同樣,先從初始化 SchedulerFactory 和 Scheduler 開始。而後,再也不須要初始化做業和觸發器,而是要獲取觸發器羣組名稱列表,以後對於每一個羣組名稱,獲取觸發器名稱列表。請注意,每一個現有的做業都應當用 Scheduler.reschedule() 方法從新調度。僅僅從新初始化在先前的應用程序運行時終止的做業,不會正確地裝載觸發器的屬性。
public void task() throws SchedulerException { // Initiate a Schedule Factory SchedulerFactory schedulerFactory = new StdSchedulerFactory(); // Retrieve a scheduler from schedule factory Scheduler scheduler = schedulerFactory.getScheduler(); String[] triggerGroups; String[] triggers; triggerGroups = scheduler.getTriggerGroupNames(); for (int i = 0; i < triggerGroups.length; i++) { triggers = scheduler.getTriggerNames(triggerGroups[i]); for (int j = 0; j < triggers.length; j++) { Trigger tg = scheduler.getTrigger(triggers[j], triggerGroups[i]); if (tg instanceof SimpleTrigger && tg.getName().equals("simpleTrigger")) { ((SimpleTrigger)tg).setRepeatCount(100); // reschedule the job scheduler.rescheduleJob(triggers[j], triggerGroups[i], tg); // unschedule the job //scheduler.unscheduleJob(triggersInGroup[j], triggerGroups[i]); } } } // start the scheduler scheduler.start(); }
在第一次運行示例時,觸發器在數據庫中初始化。圖 1 顯示了數據庫在觸發器初始化以後但還沒有擊發以前的狀況。因此,基於 清單 4 中的setRepeatCount() 方法,將 REPEAT_COUNT 設爲 100,而 TIMES_TRIGGERED 是 0。在應用程序運行一段時間以後,應用程序中止。
圖 2 顯示了數據庫在應用程序中止後的狀況。在這個圖中,TIMES_TRIGGERED 被設爲 19,表示做業運行的次數。
當再次啓動應用程序時,REPEAT_COUNT 被更新。這在圖 3 中很明顯。在圖 3 中能夠看到 REPEAT_COUNT 被更新爲 81,因此新的REPEAT_COUNT 等於前面的 REPEAT_COUNT 值減去前面的 TIMES_TRIGGERED 值。並且,在圖 3 中還看到新的 TIMES_TRIGGERED 值是 7,表示做業從應用程序從新啓動以來,又觸發了 7 次。
當再次中止應用程序以後,REPEAT_COUNT 值再次更新。如圖 4 所示,應用程序已經中止,尚未從新啓動。一樣,REPEAT_COUNT 值更新成前一個 REPEAT_COUNT 值減去前一個 TIMES_TRIGGERED 值。
正如在使用 JDBCJobStore 時看到的,能夠用許多屬性來調整 Quartz 的行爲。應當在 quartz.properties 文件中指定這些屬性。請參閱 參考資料得到能夠配置的屬性的列表。清單 5 顯示了用於 JDBCJobStore 示例的屬性:
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 10 org.quartz.threadPool.threadPriority = 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true # Using RAMJobStore ## if using RAMJobStore, please be sure that you comment out the following ## - org.quartz.jobStore.tablePrefix, ## - org.quartz.jobStore.driverDelegateClass, ## - org.quartz.jobStore.dataSource #org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore # Using JobStoreTX ## Be sure to run the appropriate script(under docs/dbTables) first to create tables org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX # Configuring JDBCJobStore with the Table Prefix org.quartz.jobStore.tablePrefix = QRTZ_ # Using DriverDelegate org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate # Using datasource org.quartz.jobStore.dataSource = qzDS # Define the datasource to use org.quartz.dataSource.qzDS.driver = com.ibm.db2.jcc.DB2Driver org.quartz.dataSource.qzDS.URL = jdbc:db2://localhost:50000/dbname org.quartz.dataSource.qzDS.user = dbuserid org.quartz.dataSource.qzDS.password = password org.quartz.dataSource.qzDS.maxConnections = 30
Quartz 做業調度框架所提供的 API 在兩方面都表現極佳:既全面強大,又易於使用。Quartz 能夠用於簡單的做業觸發,也能夠用於複雜的 JDBC 持久的做業存儲和執行。OpenSymphony 在開放源碼世界中成功地填補了一個空白,過去繁瑣的做業調度如今對開發人員來講不過是小菜一碟。
描述 | 名字 | 大小 |
---|---|---|
帶有依賴 jar 的基於 Quartz 的示例 Java 代碼 | j-quartz-withJars.zip | 3173KB |
不帶依賴 jar 的基於 Quartz 的示例 Java 代碼 | j-quartz-noJars.zip | 10KB |