程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 用Quartz進行作業調度

用Quartz進行作業調度

編輯:關於JAVA

簡介:Quartz 是個開放源碼項目,提供了豐富的作業調度集。在這篇文章中 ,軟件工程師 Michael Lipton 和 IT 架構師 Soobaek Jang 對 Quartz API 進行了介紹,從對框架的一 般概述開始,並以一系 列展示 Quart 基本特性的代碼示例作為結束。在閱讀完本文並看過代碼示例後, 您應當能夠把 Quartz 的基本特性應用到任何 Java™ 應用程序中。

現代的 Web 應用程序框架在范圍和復雜性方面都有所發展,應用程序的每個 底層組件也必須相應地發 展。作業調度是現代系統中對 Java 應用程序的一般要求,而且也是對 Java 開 發人員一貫的要求。雖然 目前的調度技術比起原始的數據庫觸發器標志和獨立的調度器線程來說,已經發 展了許多,但是作業調度 仍然不是個小問題。對這個問題最合適的解決方案就是來自 OpenSymphony 的 Quartz API。

Quartz 是個開源的作業調度框架,為在 Java 應用程序中進行作業調度提供 了簡單卻強大的機制。 Quartz 允許開發人員根據時間間隔(或天)來調度作業。它實現了作業和觸發器 的多對多關系,還能把 多個作業與不同的觸發器關聯。整合了 Quartz 的應用程序可以重用來自不同事 件的作業,還可以為一個 事件組合多個作業。雖然可以通過屬性文件(在屬性文件中可以指定 JDBC 事務 的數據源、全局作業和/ 或觸發器偵聽器、插件、線程池,以及更多)配置 Quartz,但它根本沒有與應用 程序服務器的上下文或 引用集成在一起。結果就是作業不能訪問 Web 服務器的內部函數;例如,在使用 WebSphere 應用服務器 時,由 Quartz 調度的作業並不能影響服務器的動態緩存和數據源。

本文使用一系列代碼示例介紹 Quartz API,演示它的機制,例如作業、觸發 器、作業倉庫和屬性。

入門

要開始使用 Quartz,需要用 Quartz API 對項目進行配置。步驟如下:

下載 Quartz API。

解壓縮並把 quartz-x.x.x.jar 放在項目文件夾內,或者把文件放在項目的類 路徑中。

把 core 和/或 optional 文件夾中的 jar 文件放在項目的文件夾或項目的類 路徑中。

如果使用 JDBCJobStore,把所有的 JDBC jar 文件放在項目的文件夾或項目 的類路徑中。

為了方便讀者,我已經把所有必要的文件,包括 DB2 JDBC 文件,編譯到一個 zip 文件中。請參閱 下載 小節下載代碼。

現在來看一下 Quartz API 的主要組件。

作業和觸發器

Quartz 調度包的兩個基本單元是作業和觸發器。作業 是能夠調度的可執行任 務,觸發器 提供了對作 業的調度。雖然這兩個實體很容易合在一起,但在 Quartz 中將它們分離開來是 有原因的,而且也很有益 處。

通過把要執行的工作與它的調度分開,Quartz 允許在不丟失作業本身或作業 的上下文的情況下,修改 調度觸發器。而且,任何單個的作業都可以有多個觸發器與其關聯。

示例 1:作業

通過實現 org.quartz.job 接口,可以使 Java 類變成可執行的。清單 1 提 供了 Quartz 作業的一個 示例。這個類用一條非常簡單的輸出語句覆蓋了 execute(JobExecutionContext context) 方法。這個方 法可以包含我們想要執行的任何代碼(所有的代碼示例都基於 Quartz 1.5.2,它 是編寫這篇文章時的穩 定發行版)。

清單 1. SimpleQuartzJob.java

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 對象儲存作業的偵聽 器、群組、數據映射、描 述以及作業的其他屬性。

示例 2:簡單觸發器

觸發器可以實現對任務執行的調度。Quartz 提供了幾種不同的觸發器,復雜 程度各不相同。清單 2 中的 SimpleTrigger 展示了觸發器的基礎:

清單 2. SimpleTriggerRunner.java

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。除了指定重復次數和重復間隔, 還可以指定作業在特定 日歷時間執行,只需給定執行的最長時間或者優先級(稍後討論)。執行的最長 時間可以覆蓋指定的重復 次數,從而確保作業的運行不會超過最長時間。

示例 3: Cron 觸發器

CronTrigger 支持比 SimpleTrigger 更具體的調度,而且也不是很復雜。基 於 cron 表達式, CronTrigger 支持類似日歷的重復間隔,而不是單一的時間間隔 —— 這相對 SimpleTrigger 而言是一 大改進。

Cron 表達式包括以下 7 個字段:

小時

月內日期

周內日期

年(可選字段)

特殊字符

Cron 觸發器利用一系列特殊字符,如下所示:

反斜線(/)字符表示增量值。例如,在秒字段中“5/15”代表從第 5 秒開始 ,每 15 秒一次。

問號(?)字符和字母 L 字符只有在月內日期和周內日期字段中可用。問號表 示這個字段不包含具體 值。所以,如果指定月內日期,可以在周內日期字段中插入“?”,表示周內日期 值無關緊要。字母 L 字 符是 last 的縮寫。放在月內日期字段中,表示安排在當月最後一天執行。在周 內日期字段中,如果“L ”單獨存在,就等於“7”,否則代表當月內周內日期的最後一個實例。所以“0L ”表示安排在當月的最 後一個星期日執行。

在月內日期字段中的字母(W)字符把執行安排在最靠近指定值的工作日。把 “1W”放在月內日期字段 中,表示把執行安排在當月的第一個工作日內。

井號(#)字符為給定月份指定具體的工作日實例。把“MON#2”放在周內日期 字段中,表示把任務安 排在當月的第二個星期一。

星號(*)字符是通配字符,表示該字段可以接受任何可能的值。

所有這些定義看起來可能有些嚇人,但是只要幾分鐘練習之後,cron 表達式 就會顯得十分簡單。

清單 3 顯示了 CronTrigger 的一個示例。請注意 SchedulerFactory、 Scheduler 和 JobDetail 的 實例化,與 SimpleTrigger 示例中的實例化是相同的。在這個示例中,只是修改 了觸發器。這裡指定的 cron 表達式(“0/5 * * * * ?”)安排任務每 5 秒執行一次。

清單 3. CronTriggerRunner.java

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 的一些高級特 性。

作業倉庫

Quartz 提供了兩種不同的方式用來把與作業和觸發器有關的數據保存在內存 或數據庫中。第一種方式 是 RAMJobStore 類的實例,這是默認設置。這個作業倉庫最易使用,而且提供了 最佳性能,因為所有數 據都保存在內存中。這個方法的主要不足是缺乏數據的持久性。因為數據保存在 RAM 中,所以應用程序 或系統崩潰時,所有信息都會丟失。

為了修正這個問題,Quartz 提供了 JDBCJobStore。顧名思義,作業倉庫通過 JDBC 把所有數據放在 數據庫中。數據持久性的代價就是性能降低和復雜性的提高。

設置 JDBCJobStore

在前面的示例中,已經看到了 RAMJobStore 實例的工作情況。因為它是默認 的作業倉庫,所以顯然不 需要額外設置就能使用它。但是,使用 JDBCJobStore 需要一些初始化。

在應用程序中設置使用 JDBCJobStore 需要兩步:首先必須創建作業倉庫使用 的數據庫表。 JDBCJobStore 與所有主流數據庫都兼容,而且 Quartz 提供了一系列創建表的 SQL 腳本,能夠簡化設置 過程。可以在 Quartz 發行包的 “docs/dbTables”目錄中找到創建表的 SQL 腳 本。第二,必須定義一 些屬性,如表 1 所示:

表 1. JDBCJobStore 屬性

屬性名稱 值 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() 方法重新調度。僅僅重新初始化在先前的應用程序運行 時終止的作業,不會正 確地裝載觸發器的屬性。

清單 4. JDBCJobStoreRunner.java

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();
   }

運行 JDBCJobStore

在第一次運行示例時,觸發器在數據庫中初始化。圖 1 顯示了數據庫在觸發 器初始化之後但尚未擊發 之前的情況。所以,基於 清單 4 中的 setRepeatCount() 方法,將 REPEAT_COUNT 設為 100,而 TIMES_TRIGGERED 是 0。在應用程序運行一段時間之後,應用程序停止。

圖 1. 使用 JDBCJobStore 時數據庫中的數據(運行前)

圖 2 顯示了數據庫在應用程序停止後的情況。在這個圖中,TIMES_TRIGGERED 被設為 19,表示作業 運行的次數。

圖 2. 同一數據在 19 次迭代之後

當再次啟動應用程序時,REPEAT_COUNT 被更新。這在圖 3 中很明顯。在圖 3 中可以看到 REPEAT_COUNT 被更新為 81,所以新的 REPEAT_COUNT 等於前面的 REPEAT_COUNT 值減去前面的 TIMES_TRIGGERED 值。而且,在圖 3 中還看到新的 TIMES_TRIGGERED 值是 7, 表示作業從應用程序重新 啟動以來,又觸發了 7 次。

圖 3. 第 2 次運行 7 次迭代之後的數據

當再次停止應用程序之後,REPEAT_COUNT 值再次更新。如圖 4 所示,應用程 序已經停止,還沒有重 新啟動。同樣,REPEAT_COUNT 值更新成前一個 REPEAT_COUNT 值減去前一個 TIMES_TRIGGERED 值。

圖 4. 再次運行觸發器之前的初始數據

使用屬性

正如在使用 JDBCJobStore 時看到的,可以用許多屬性來調整 Quartz 的行為 。應當在 quartz.properties 文件中指定這些屬性。清單 5 顯示了用於 JDBCJobStore 示 例的屬性:

清單 5. quartz.properties

org.quartz.threadPool.class =  org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThr ead =  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 在開放源碼世界中 成功地填補了一個空白,過去繁瑣的作業調度現在對開發人員來說不過是小菜一 碟。

來源:http://www.ibm.com/developerworks/cn/java/j-quartz/

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved