用 Quartz 進行做業調度


用 Quartz 進行做業調度

Quartz API 採用多面方式在 Java 應用程序中進行任務調度 php

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

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



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

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

入門

要開始使用 Quartz,須要用 Quartz API 對項目進行配置。步驟以下: 編程

  1. 下載 Quartz API
  2. 解壓縮並把 quartz-x.x.x.jar 放在項目文件夾內,或者把文件放在項目的類路徑中。
  3. 把 core 和/或 optional 文件夾中的 jar 文件放在項目的文件夾或項目的類路徑中。
  4. 若是使用 JDBCJobStore,把全部的 JDBC jar 文件放在項目的文件夾或項目的類路徑中。

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

如今來看一下 Quartz API 的主要組件。 服務器

做業和觸發器

Quartz 調度包的兩個基本單元是做業和觸發器。做業 是可以調度的可執行任務,觸發器 提供了對做業的調度。雖然這兩個實體很容易合在一塊兒,但在 Quartz 中將它們分離開來是有緣由的,並且也頗有益處。 架構

經過把要執行的工做與它的調度分開,Quartz 容許在不丟失做業自己或做業的上下文的狀況下,修改調度觸發器。並且,任何單個的做業均可以有多個觸發器與其關聯。 app


示例 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 時數據庫中的數據(運行前)
在用 JDBCJobStore 運行前

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

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

當再次啓動應用程序時,REPEAT_COUNT 被更新。這在圖 3 中很明顯。在圖 3 中能夠看到 REPEAT_COUNT 被更新爲 81,因此新的REPEAT_COUNT 等於前面的 REPEAT_COUNT 值減去前面的 TIMES_TRIGGERED 值。並且,在圖 3 中還看到新的 TIMES_TRIGGERED 值是 7,表示做業從應用程序從新啓動以來,又觸發了 7 次。

圖 3. 第 2 次運行 7 次迭代以後的數據
第 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.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

參考資料

學習

得到產品和技術

  • Download Quartz:Java 應用程序的開放源碼做業調度解決方案。

討論

相關文章
相關標籤/搜索