Quartz教程詳情

Quartz學習

介紹Quartz

Quartz是一個開源的任務調度系統,它能用來調度很多任務的執行。

運行環境

· Quartz 能嵌入在其他應用程序裏運行。 

· Quartz 能在一個應用服務器裏被實例化(或servlet容器), 並且參與XA事務

· Quartz能獨立運行(通過JVM,或者通過RMI 

· Quartz能被集羣實例化

任務調度

當一個指定給任務的觸發器發生時,任務就被調度執行. 觸發器能被創建爲:

· 一天的某個時間(精確到毫秒級) 

· 一週的某些天 

· 一個月的某些天 

· 一年的某些天 

· 不在一個Calendar列出的某些天 (例如工作節假日) 

· 在一個指定的次數重複

· 重複到一個指定的時間/日期

· 無限重複 

· 在一個間隔內重複

能夠給任務指定名稱和組名.觸發器也能夠指定名稱和組名,這樣可以很好的在調度器裏組織起來.一個加入到調度器裏的任務可以被多個觸發器註冊。在J2EE環境裏,任務能作爲一個分佈式(XA)事務的一部分來執行。

任務執行

· 任務能夠是任何實現Job接口的Java類。 

· 任務類能夠被Quartz實例化,或者被你的應用框架。 

· 當一個觸發器觸發時,調度器會通知實例化了JobListener 和TriggerListener 接口的0個或者多個Java對象(監聽器可以是簡單的Java對象, EJBs, 或JMS發佈者等). 在任務執行後,這些監聽器也會被通知。 

· 當任務完成時,他們會返回一個JobCompletionCode ,這個代碼告訴調度器任務執行成功或者失敗.這個代碼 也會指示調度器做一些動作-例如立即再次執行任務。 

任務持久化

· Quartz的設計包含JobStore接口,這個接口能被實現來爲任務的存儲提供不同的機制。 

· 應用JDBCJobStore, 所有被配置成“穩定”的任務和觸發器能通過JDBC存儲在關係數據庫裏。 

· 應用RAMJobStore, 所有任務和觸發器能被存儲在RAM裏因此不必在程序重起之間保存-一個好處就是不必使用數據庫。 

事務

· 使用JobStoreCMT(JDBCJobStore的子類),Quartz 能參與JTA事務。 

· Quartz 能管理JTA事務(開始和提交)在執行任務之間,這樣,任務做的事就可以發生在JTA事務裏。 

集羣

· Fail-over. 

· Load balancing. 

監聽器和插件

· 通過實現一個或多個監聽接口,應用程序能捕捉調度事件來監控或控制任務/觸發器的行爲。 

· 插件機制可以給Quartz增加功能,例如保持任務執行的歷史記錄,或從一個定義好的文件里加載任務和觸發器。 

· Quartz 裝配了很多插件和監聽器。

1.使用Quartz

在我們用調度器之前,調度器需要實例化。我們用SchedulerFactory 來實例它。一旦調度器被實例,我們就可以啓動它,置它爲stand-by模式,最後關閉它。注意:一旦一個調度器被關閉了,如果我們不重新實例化它,它就不可能被再次啓動。直到調度器啓動了或者當調度器處於暫停狀態,觸發器才能夠觸發。下面有個簡單的例子:

SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();

Scheduler sched = schedFact.getScheduler();

sched.start();

JobDetail jobDetail = new JobDetail("myJob", null, DumbJob.class);

Trigger trigger = TriggerUtils.makeHourlyTrigger(); // 每個小時觸發

trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date())); // 在下個小時開始

trigger.setName("myTrigger");

sched.scheduleJob(jobDetail, trigger);

就象你看到的,使用Quartz是很簡單的。在下一節我們介紹JobsTriggers

2.Jobs 和 Triggers

就象以前提到的,一個實現了Job接口的Java類就能夠被調度器執行。接口如下:

package org.quartz;

public interface Job {

public void execute(JobExecutionContext context) throws JobExecutionException;

}

很簡的,當Jobtrigger觸發時,Jobexecute(..)方法就會被調度器調用。被傳遞到這個方法裏來的JobExecutionContext對象提供了帶有job運行時的信息:執行它的調度器句柄、觸發它的觸發器句柄、jobJobDetail對象和一些其他的項。

JobDetail對象是Job在被加到調度器裏時所創建的,它包含有很多的Job屬性設置,和JobDataMap一樣,可以用來存儲job實例時的一些狀態信息。

Trigger對象是用來觸發執行Job的。當調度一個job時,我們實例一個觸發器然後調整它的屬性來滿足job執行的條件。觸發器也有一個和它相關的JobDataMap,它是用來給被觸發器觸發的job傳參數的。Quartz有一些不同的觸發器類型,不過,用得最多的是SimpleTrigger和CronTrigger。 

如果我們需要在給定時刻執行一次job或者在給定時刻觸發job隨後間斷一定時間不停的執行的話,SimpleTrigger是個簡單的解決辦法;如果我們想基於類似日曆調度的觸發job的話,比如說,在每個星期五的中午或者在每個月第10天的1015觸發job時,CronTrigger是很有用的。

爲什麼用jobstriggers呢?很多任務調度器並沒有任務和觸發器的概念,一些任務調度器簡單定義一個“job”爲在一個執行時間伴隨一些小任務標示,其他的更像Quartzjobtrigger對象的聯合體。在開發Quartz時,開發者們決定,在調度時間表和在這上面運行的工作應該分開。這是很有用的。

例如,job能夠獨立於觸發器被創建和儲存在任務調度器裏,並且,很多的觸發器能夠與同一個job關聯起來。這個鬆耦合的另一個好處就是在與jobs關聯的觸發器終止後,我們能夠再次配置保留在調度器裏的jobs,這樣的話,我們能夠再次調度這些jobs而不需要重新定義他們。我們也可以在不重新定義一個關聯到job的觸發器的情況下,修改或替代它。

Jobstriggers被註冊到Quartz的調度器裏時,他們就有了唯一標示符。他們也可以被放到“groups”裏,Groups是用來組織分類jobstriggers的,以便今後的維護。在一個組裏的jobtrigger的名字必須是唯一的,換句話說,一個jobtrigger的全名爲他們的名字加上組名。如果把組名置爲”null”,系統會自動給它置爲Scheduler.DEFAULT_GROUP

現在,我們大概有了一些jobstriggers的理解,隨後2節我們將更多的瞭解它們。

3.更多關於Jobs & JobDetails

Jobs很容易實現,這兒有更多我們需要理解的東西:jobs的本質,job接口的execute(..)方法,關於JobDetails。

當我們實現的一個class是真正的”job”時,Quartz需要知道各種job有的屬性,這是通過JobDetail類做到的。在沒用JobDetail之前,JobDetail的功能的實現是通過在每個job的實現類上加上所有的現在JobDetail的get方法來實現的。這就在每個job類上強加了一些實現一樣功能的代碼,就顯得每個job類很笨重,於是,Quartz開發者們就創造了JobDetail類。

現在,我們來討論一下在Quartzjob的本質和job實例的生命週期。首先我們來看看第一節的代碼片段:  

JobDetail jobDetail = new JobDetail("myJob",      // job 名稱

                     sched.DEFAULT_GROUP, // job組名(可以寫'null'來用default group)

                     DumbJob.class);         //要執行的java

Trigger trigger = TriggerUtils.makeDailyTrigger(8, 30);

trigger.setStartTime(new Date());

trigger.setName("myTrigger");

sched.scheduleJob(jobDetail, trigger);

現在我們定義“DumbJob”類:

public class DumbJob implements Job {

    public DumbJob() {

    }

    public void execute(JobExecutionContext context)

      throws JobExecutionException

    {

      System.err.println("DumbJob is executing.");

    }

}

可以看到我們給調度器一個JobDetail實例,並且,它通過job的類代碼引用這個job來執行。每次調度器執行job時,它會在調用jobexecute(..)方法之前創建一個他的實例。這就帶來了兩個事實:一、job必須有一個不帶參數的構造器,二、在job類裏定義數據成員並沒有意義,因爲在每次job執行的時候他們的值會被覆蓋掉。

你可能現在想要問“我怎樣給一個job實例提供屬性/配置?”和“在幾次執行間我怎樣能跟蹤job的狀態?”這些問題的答案是一樣的:用JobDataMap- JobDetail對象的一部分。

JobDataMap

JobDataMap能夠支持任何序列化的對象,當job執行時,這些對象能夠在job實例中可用。JobDataMap實現了Java Map接口,它有一些附加的方法,這些方法用來儲存和跟蹤簡單類型的數據。

如下代碼可以很快地給job增加JobDataMap:

jobDetail.getJobDataMap().put("jobSays", "Hello World!");

jobDetail.getJobDataMap().put("myFloatValue", 3.141f);

jobDetail.getJobDataMap().put("myStateData", new ArrayList());

job執行時,我們可以在job裏通過如下代碼得到JobDataMap:

public class DumbJob implements Job {

    public DumbJob() {

    }

    public void execute(JobExecutionContext context)

      throws JobExecutionException

    {

      String instName = context.getJobDetail().getName();

      String instGroup = context.getJobDetail().getGroup();

      JobDataMap dataMap = context.getJobDetail().getJobDataMap();

      String jobSays = dataMap.getString("jobSays");

      float myFloatValue = dataMap.getFloat("myFloatValue");

      ArrayList state = (ArrayList)dataMap.get("myStateData");

      state.add(new Date());

      System.err.println("Instance " + instName + " of DumbJob says: " + jobSays);

    }

  }

如果用一個持久JobStore(在指南JobStore章節討論),我們就應該注意在JobDataMap裏放些什麼,因爲在它裏面的對象將會被序列化,並且這些對象會因此產生一些class-versioning問題。明顯的,標準Java類型應該是很安全的,但是,任何時候某人改變了一個你已經序列化的實例的類的定義時,我們就要注意不能夠破壞兼容性了。在這個方面的進一步信息可以在Java Developer Connection Tech Tip: Serialization In The Real World裏找到。我們能把JDBC-JobStore和JobDataMap放到一個模式裏,在那裏,只有簡單類型和String型能被儲存在Map裏,從而消去任何以後的序列化問題。

Stateful vs. Non-Stateful Jobs

觸發器也有與它們關聯的JobDataMaps。假設我們有一個儲存在調度器裏被多個觸發器關聯的job,然而,對於每個獨立的觸發器,我想提供給job不同的數據輸入,在這個時候,JobDataMaps就很有用了。

job執行期間,JobDataMaps能夠在JobExecutionContext裏獲得。JobDataMap融合在Trigger和JobDetail類裏,JobDataMap裏面的值能夠利用key來更新。

以下例子顯示,在job執行期間從JobExecutionContext裏的JobDataMap得到數據:

public class DumbJob implements Job {

public DumbJob() {

}

    public void execute(JobExecutionContext context)

      throws JobExecutionException

    {

      String instName = context.getJobDetail().getName();

      String instGroup = context.getJobDetail().getGroup();

      JobDataMap dataMap = context.getJobDataMap();  // 注意:不同於以前的例子

      String jobSays = dataMap.getString("jobSays");

      float myFloatValue = dataMap.getFloat("myFloatValue");

      ArrayList state = (ArrayList)dataMap.get("myStateData");

      state.add(new Date());

      System.err.println("Instance " + instName + " of DumbJob says: " + jobSays);

    }

  }

StatefulJob

現在,關於job狀態數據的一些附加要點:一個job實例能定義爲"有狀態的"或者"無狀態的"。無狀態的jobs僅當它們在被加入到調度器裏時才存儲JobDataMap。這就意味着,在jobs執行期間對JobDataMap裏數據的任何改變都會丟失,下次執行時job將看不到這些數據。你可能會猜到,一個有狀態的job就是它的反面例子-它的JobDataMap是在每次執行完job後再次儲存的。一個缺點就是有狀態的job不能夠併發執行。換句話說,如果job是有狀態的,一個觸發器嘗試觸發這個已經執行了的job時,這個觸發器就會等待直到這次執行結束。

用實現StatefulJob 接口來標記一個job是有狀態的。

Job 'Instances'

我們能夠創建一個單獨的job類,並且通過創建多個JobDetails實例在調度器裏儲存很多它的“實例定義”,每個都有它自己的屬性集和JobDataMap ,把它們都加入到調度器裏。

當一個觸發器觸發時,與它關聯的job就是通過配置在調度器上的JobFactory 來實例化的。默認的JobFactory 簡單的調用在job類上的newInstance()方法,你可能想要創建自己的JobFactory實現來完成一些自己想要的事情,如:擁有應用程序的IoC或者DI容器進程/初始化job實例。

job的其他屬性

這兒有一個其他屬性的總結,這些屬性是通過JobDetail對象爲一個job實例定義的。

· 持久性– 如果一個job是非持久的,一旦沒有任何可用的觸發器與它關聯時,他就會自動得從調度器裏被刪除。 

· 不穩定性-如果一個job是不穩定的,他就不會在重起Quartz調度器之間持久化。

· 請求恢復– 如果一個job“請求恢復”,在調度器“硬關閉”(如:該進程崩潰,機器被關掉)時這個job還在執行,過後,當調度器再次啓動時,他就會再次執行。在這種情況下,JobExecutionContext.isRecovering() 方法將會返回true. 

· Job監聽器 –一個job能夠有0個或者多個與它關聯的監聽器。當job執行時,監聽器就會被通知。在監聽器的更多討論請看TriggerListeners & JobListeners

JobExecutionException

最後,我們來看看Job.execute(..)方法的一些細節。你能夠從execute方法裏拋出的僅有的異常類型就是JobExecutionException。因爲這樣,我們應該使用try-catch塊包圍整個execute方法內容。我們還應該花一些時間看看JobExecutionException文檔。當job執行發生異常時,通過設置JobExecutionException,可以讓此job再次進入調度器或者今後不再運行。

4.更多關於Triggers

jobs一樣,triggers也相對來說很容易。但是,我們還是要理解它的一些特性。Quartz裏也有很多類型的trigger提供給我們使用。

Calendars

Quartz Calendar 對象(不是java.util.Calendar對象)能夠在trigger儲存在調度器時和trigger關聯起來。Calendars主要用來在trigger配置時排除一些時間。例如,你能夠創建一個在每個工作日早上930觸發的trigger,然後爲這個trigger增加一個排除所有商業的節假日的Calendar。

Calendars能夠是任何序列化的對象,只要這些對象實現了Calendar接口: 

package org.quartz;

  public interface Calendar {

    public boolean isTimeIncluded(long timeStamp);

    public long getNextIncludedTime(long timeStamp);

  }

注意到這些方法的參數類型是long。這意味着calendars能夠排除毫秒級的時間段。大部分地,我們感興趣的是一整天的,所以在Quartz裏,有個實現類提供了方便:org.quartz.impl.HolidayCalendar

Calendars必須被實例化並且通過addCalendar(..)方法註冊到調度器裏。如果你用HolidayCalendar,在實例它之後,你應該用它的addExcludedDate(Date date)方法以便組裝上你想排除的那幾天。一個calendar實例能夠被多個triggers使用:

HolidayCalendar cal = new HolidayCalendar();

  cal.addExcludedDate( someDate );

  sched.addCalendar("myHolidays", cal, false);

  Trigger trigger = TriggerUtils.makeHourlyTrigger(); // 每小時觸發

  trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date()));  //下一個小時開始  trigger.setName("myTrigger1");

  trigger.setCalendarName("myHolidays");

  // .. schedule job with trigger

  Trigger trigger2 = TriggerUtils.makeDailyTrigger(8, 0); // 每天早上8點觸發

  trigger2.setStartTime(new Date()); //立即開始

  trigger2.setName("myTrigger2");

  trigger2.setCalendarName("myHolidays");

  // .. schedule job with trigger2

不觸發(misfire)指令

觸發器的另外一個重要的屬性是“不觸發指令”。如果一個持久的觸發器由於調度器被關閉了而沒有找到它的觸發時間,那麼一個不觸發將會發生。不同的觸發器類型有不同的不觸發指令。默認的,他們都用“smart policy”指令-這是一個基於觸發器類型和配置的動態行爲。當調度器啓動時,他將會搜尋所有沒觸發的持久化的triggers,然後基於他們各個配置的不觸發指令來更新他們。當你用Quartz,你應該熟悉各個不觸發指令,我們在以下章節有一些介紹。給一個trigger實例配置不觸發指令,要用此實例的setMisfireInstruction(..)方法。

TriggerUtils - Triggers Made Easy

TriggerUtils類(在org.quartz包裏)包含了很多方便的工具。能夠幫你創建triggersdatas。用這個類能夠很容易製造一些trigges,這些triggers能夠在每分鐘,每小時,每週,每個月等等觸發。用它也能產生一些接近某個秒、分鐘、小時的天-這在設置trigger的啓動時間很有幫助。

TriggerListeners

最後,triggers有一些註冊了的監聽器,象job一樣。實現了TriggerListener接口的對象將接受一個trigger被觸發的通知。

5. SimpleTrigger

詳細介紹一下它的構造器:

public SimpleTrigger(String name, //trigger名稱

                  String group, //trigger的組名

                  Date startTime, //開始時間

                  Date endTime, //結束時間

                   int repeatCount, //重複次數

                   long repeatInterval)//重複間隔

舉幾個常用例子:

從現在開始10秒後執行一次:

long startTime = System.currentTimeMillis() + 10000L;

  SimpleTrigger trigger = new SimpleTrigger("myTrigger",

                                            null,

                                            new Date(startTime),

                                            null,

                                            0,

0L);

立即執行,60秒間隔無限制重複:

SimpleTrigger trigger = new SimpleTrigger("myTrigger",

                                            null,

                                            new Date(),

                                            null,

                                            SimpleTrigger.REPEAT_INDEFINITELY,

60L * 1000L);

從現在開始立即執行,每10秒重複,直到40秒後:

long endTime = System.currentTimeMillis() + 40000L;

  SimpleTrigger trigger = new SimpleTrigger("myTrigger",

                                            "myGroup",

                                            new Date(),

                                            new Date(endTime),

                                            SimpleTrigger.REPEAT_INDEFINITELY,

10L * 1000L);

20023171030am觸發,重複5次(一共6次),30秒間隔:

java.util.Calendar cal = new java.util.GregorianCalendar(2002, cal.MARCH, 17);

  cal.set(cal.HOUR, 10);

  cal.set(cal.MINUTE, 30);

  cal.set(cal.SECOND, 0);

  cal.set(cal.MILLISECOND, 0);

  Data startTime = cal.getTime();

  SimpleTrigger trigger = new SimpleTrigger("myTrigger",

                                            null,

                                            startTime,

                                            null,

                                            5,

                                            30L * 1000L);

SimpleTrigger 不觸發指令

MISFIRE_INSTRUCTION_FIRE_NOW

MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT

MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT

MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT

MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT

6.CronTrigger

構造器

CronTrigger(String name, //觸發器名稱

String group, //觸發器的組名 

String jobName, //job名稱

String jobGroup, //job的組名

Date startTime, //開始時間

Date endTime, //結束時間

String cronExpression, //克隆表達式

TimeZone timeZone)//時區

還有一些其它參數少一些的構造器,參考JavaDoc。通常我們如下簡單地使用CronTrigger;

Trigger trigger = new CronTrigger("trigger1", "group1");//設置觸發器名稱和組名

trigger.setCronExpression("0 0 15 * * ?");//設置克隆表達式

克隆表達式

一個克隆表達式是一個由空白間隔6個或者7個字段的字符串。

格式:

字段名

必須有?

值範圍

允許的特殊字符

Seconds

YES

0-59

, - * /

Minutes

YES

0-59

, - * /

Hours

YES

0-23

, - * /

Day of month

YES

1-31

, - * ? / L W C

Month

YES

1-12 or JAN-DEC

, - * /

Day of week

YES

1-7 or SUN-SAT

, - * ? / L C #

Year

NO

empty, 1970-2099

, - * /

例子:

* * * * ? *

0 0/5 14,18,3-39,52 ? JAN,MAR,SEP MON-FRI 2002-2010

特殊字符

· * 表示所有值 ;

· ? 表示未說明的值,即不關心它爲何值; 

· - 表示一個指定的範圍; 

· , 表示附加一個可能值; 

· / 符號前表示開始時間,符號後表示每次遞增的值; 

· L ("last") "L" 用在day-of-month字段意思是 "這個月最後一天";用在 day-of-week字段, 它簡單意思是 "7" or "SAT"。 如果在day-of-week字段裏和數字聯合使用,它的意思就是 "這個月的最後一個星期幾" – 例如: "6L" means "這個月的最後一個星期五". 當我們用“L”時,不指明一個列表值或者範圍是很重要的,不然的話,我們會得到一些意想不到的結果。 

· W ("weekday") –只能用在day-of-month字段。用來描敘最接近指定天的工作日(週一到週五)。例如:在day-of-month字段用“15W”指“最接近這個月第15天的工作日”,即如果這個月第15天是週六,那麼觸發器將會在這個月第14天即週五觸發;如果這個月第15天是週日,那麼觸發器將會在這個月第16天即週一觸發;如果這個月第15天是週二,那麼就在觸發器這天觸發。注意一點:這個用法只會在當前月計算值,不會越過當前月。“W”字符僅能在day-of-month指明一天,不能是一個範圍或列表。

也可以用“LW”來指定這個月的最後一個工作日。

· # -只能用在day-of-week字段。用來指定這個月的第幾個周幾。例:在day-of-week字段用"6#3"指這個月第3個週五(6指週五,3指第3個)。如果指定的日期不存在,觸發器就不會觸發。 

· C ("calendar") – 指和calendar聯繫後計算過的值。例:在day-of-month 字段用“5C”指在這個月第5天或之後包括calendar的第一天;在day-of-week字段用“1C”指在這週日或之後包括calendar的第一天。 

MONTHDay of week字段裏對字母大小寫不敏感。

一些例子

表達式

意思(觸發時刻)

0 0 12 * * ?

每天中午12點

0 15 10 * * ? 2005

在2005年的每天10:25

0 10,44 14 ? 3 WED

在3月裏每個週三的14:10和14:44

0 15 10 ? * 6L 2002-2005

從2002年到2005年裏,每個月的最後一個星期五的10:15

0 0 12 1/5 * ?

從當月的第一天開始,然後在每個月每隔5天的12:00

0 15 10 ? * 6#3

每個月第3個週五的10:15

注意在day-of-week和day-of-month字段裏使用“?”和“*”的效果。

注意

· 對“C”的支持並不很完全。 

· 對在day-of-week字段 和在day-of-month字段同時使用也不是很完全(目前你必須在這兩個字段中的一個用“?”指定)。 

· 當設置在午夜和凌晨1點之間觸發時要仔細。

不觸發指令:

MISFIRE_INSTRUCTION_FIRE_ONCE_NOW

MISFIRE_INSTRUCTION_DO_NOTHING

7.TriggerListeners 和JobListeners

Trigger相關的事件有:觸發器觸發,觸發器的不觸發(參考先前章節),觸發器完成。

public interface TriggerListener {

    public String getName();

    public void triggerFired(Trigger trigger, JobExecutionContext context);

    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);

    public void triggerMisfired(Trigger trigger);

public void triggerComplete(Trigger trigger, JobExecutionContext context,

             int triggerInstructionCode);

}

job相關的事件有:job準備執行,job執行完畢。

public interface JobListener {

    public String getName();

    public void jobToBeExecuted(JobExecutionContext context);

    public void jobExecutionVetoed(JobExecutionContext context);

    public void jobWasExecuted(JobExecutionContext context,

             JobExecutionException jobException);

}

使用Listeners

創建一個監聽器,就是創建一個實現了org.quartz.TriggerListener 和 org.quartz.JobListener接口的對象。在運行的期間用調度器註冊監聽器,必須要給它提供一個名字。監聽器能夠註冊成爲全局的或者不是全局的,全局監聽器接受所有的事件,而非全局的則僅接受指定給triggers/jobs了的事件。

監聽器是在運行期間被調度器註冊的,他們沒有伴隨jobstriggers儲存在JobStore裏。Jobstriggers僅儲存和它們相關的監聽器的名字。因此,每次程序運行時,監聽器需要被調度器再次註冊。

scheduler.addGlobalJobListener(myJobListener);

scheduler.addJobListener(myJobListener);

監聽器在Quartz並不是經常使用的。

8.SchedulerListeners

和調度器相關的事件有:job/trigger的加入和移出,一些調度器裏的錯誤,調度器關閉等等。

public interface SchedulerListener {

    public void jobScheduled(Trigger trigger);

    public void jobUnscheduled(String triggerName, String triggerGroup);

    public void triggerFinalized(Trigger trigger);

    public void triggersPaused(String triggerName, String triggerGroup);

    public void triggersResumed(String triggerName, String triggerGroup);

    public void jobsPaused(String jobName, String jobGroup);

    public void jobsResumed(String jobName, String jobGroup);

    public void schedulerError(String msg, SchedulerException cause);

    public void schedulerShutdown();

}

創建和註冊SchedulerListeners和其他監聽器一樣,全局和非全局的沒有區別。

9.JobStores

JobStore負責保存所有配置到調度器裏的工作數據:jobstriggerscalendars等等。在用SchedulerFactory得到一個調度器的實例時,我們可以給SchedulerFactory提供一個屬性文件或者一個屬性對象來聲明使用哪個JobStore

注意,不要在代碼裏使用JobStore的實例,這些Quartz都做好了。我們要做的就僅僅告訴Quartz(通過配置)用哪個JobStore,然後就調用Scheduler接口函數了。

RAMJobStore

利用內存來持久化調度程序信息。這種作業存儲類型最容易配置、構造和運行,但是當應用程序停止運行時,所有調度信息將被丟失。

在屬性文件裏指定:

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

JDBCJobStore

支持的數據庫有:Oracle, MySQL, MS SQLServer2000, HSQLDB, PostreSQL and DB2。使用JDBCJobStore,首先要在數據庫裏建一些Quartz要使用的表。我們可以使用Quartz發佈包裏的建表腳本,在docs/dbTables目錄下。如果沒有你所要的數據庫類型的腳本,可以在已有的腳本作一些修改。所有這些標都是以“QRTZ_”作爲前綴的,這個前綴是可以在屬性文件裏更改的。在爲多個調度器實例創建多個系列的表時,用不同的前綴是很有用的。

一旦我們創建了這些表,在配置和觸發JDBCJobStore之前就要做更多的事情了。我們需要決定應用需要哪種類型的事務處理。如果我們不需要給其他的事務處理一些調度命令(增加刪除trigger),我們就可以讓Quartz利用JobStoreTX處理這個事務(這用的很多)。

如果我們需要Quartz和其他的事務處理(在J2EE應用服務器裏)一起工作,我們就應該用JobStoreCMT-這會使Quartz讓應用服務器容器管理事務。

最後一點是從哪個JDBCJobStore啓動數據庫能夠得到該數據庫的連接。在屬性文件裏是用一個不同的方法來定義數據源的。一種是Quartz自己創建和管理數據源-提供所有的數據庫連接信息;另外一種是利用應用服務器管理的數據源,其中Quartz運行在這個應用服務器裏-JDBCJobStore提供數據庫的JNDI名稱。

用JDBCJobStore(假設我們是用的StdSchedulerFactory),我們首先要設置org.quartz.jobStore.class屬性爲org.quartz.impl.jdbcjobstore.JobStoreTX或者org.quartz.impl.jdbcjobstore.JobStoreCMT,這取決於我們的選擇。

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

下一步,我們需要選擇一個驅動代理。StdJDBCDelegate是一個用“vanilla”JDBC代碼實現的代理。如果沒有其他爲你數據庫指定的代理,就使用這個。Quartz開發者們解決的問題都是根據這個代理的來實現的。其他的代理在org.quartz.impl.jdbcjobstore包或者子包裏。包括DB2v6Delegate(DB2 version 6 或早期版本使用的),HSQLDBDelegate(HSQLDB使用),MSSQLDelegate(microsoft SQLServer 2000使用),PostgreSQLDelegate(PostgreSQL 7.x使用),WeblogicDelegate(Weblogic的JDBC驅動器使用),OracleDelegate(Oracle 8i and 9i使用)。

org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

在下一步,我們要配置表的前綴:

org.quartz.jobStore.tablePrefix = QRTZ_

最後,我們需要設置用哪個數據源,數據源的名稱必須在Quartz屬性裏定義好。例如,我們可以給Quartz指定使用“myDS”(在配置屬性裏的其他地方定義好了)作爲數據源的名字。

org.quartz.jobStore.dataSource = myDS

如果調度器很繁忙(例如,執行job的個數和線程池的大小一樣),那麼我們應該設置數據源的連接個數在線程池大小+1之上。

org.quartz.jobStore.useProperties這個屬性能夠設置爲“true”(默認爲false),用來指示JDBCJobStore:在JobDataMaps裏的所有值都應該是String,這樣在能作爲name-value方式儲存,而不是在BLOB列裏以序列化的格式儲存複雜的對象。從長遠看,這樣做會很安全,因爲你可以避免將非String的類序列化到BLOB裏的類版本問題。

10.配置,資源使用和調度器工廠

Quartz是以標準組件的方式組織的,所以,使它運行起來,一些組件需要被聯合起來。

Quartz能夠工作之前,需要配置的主要組件有:

· 線程池 

· 作業儲存 

· 數據源(需要的話)

· 調度器自己

在運行jobs時,線程池爲Quartz提供了一系列的線程。在線程池裏的線程越多,能夠並行執行的jobs就越多。但是,太多的線程會使系統癱瘓。大部分的Quartz用戶發現,5個線程就足夠了-因爲他們在指定時間裏只有少於100jobs,這些jobs並不都是在同一時刻執行,jobs完成得也很快的。其他的用戶發現他們需要101550或者100個線程-因爲他們在不同的調度器裏用了上萬個觸發器,在給定的時間裏,平均在10100jobs試着執行。爲調度器找到合適的線程數量完全依賴於你用調度起來做什麼。不在乎線程數量,而要確保你有足夠的線程來使jobs執行。如果一個觸發器的觸發時間到來了,可是沒有一個能夠用的線程,Quartz將會等到可用線程的來臨,然後job將會在幾毫秒後執行。這可能會引起不觸發-如果不在屬性文件裏給調度器配置“misfire threshold”的話。

線程池接口是在org.quartz.spi包裏定義的,你能夠創建一個線程池以自己的方法。Quartz裝配了一個簡單(但是很好的)的線程池,是org.quartz.simpl.SimpleThreadPool。這個線程池簡單的維護一些在池裏固定的線程-不會增加也不會減少。但是它能夠做很多事而且經過測試了的,幾乎每個Quartz用戶用這個線程池。

  JobStores 和 DataSrouces在前面討論過了,這裏值得一提的是,所有JobStores都實現了org.quartz.spi.JobStore接口,如果在打包裏的任何一個JobStore不能夠滿足你的需求的話,你可以自己做一個。

最後,你需要創建你的Scheduler實例。Scheduler需要提供他的名稱,說明RMI的設置,處理JobStore和ThreadPool的實例。RMI設置包括調度器是否作爲一個RMI服務器而創建。StdSchedulerFactory也能夠產生調度器的實例,這些實例實際上是創建在遠程進程中的調度器代理(RMI樁)。

StdSchedulerFactory

StdSchedulerFactory實現了org.quartz.SchedulerFactory接口。它用了一系列的屬性(java.util.Properties)來創建和初始化一個Quartz的調度器。這些屬性通常保存和加載在一個文件裏,但是也可以通過你的程序創建直接交給工廠處理。在工廠上調用getScheduler()就可以產生調度器,初始化它(還有線程池,JobStore和數據源),然後返回一個句柄到這個公共的接口。

// 默認調度器是quartz.propeties文件定義的,這個文件可以在當前目錄下找到,也可以在//classpath裏找到,如果都找不到了,就用quartz.jar裏的quartz.propeties文件。

SchedulerFactory sf = new StdSchedulerFactory();

Scheduler scheduler = sf.getScheduler();

scheduler.start();

用指定的屬性對象初始化:

SchedulerFactory sf = new StdSchedulerFactory();

sf.initialize(schedulerProperties);// schedulerProperties是屬性對象

Scheduler scheduler = sf.getScheduler();

scheduler.start();

用指定的屬性文件初始化:

SchedulerFactory sf = new StdSchedulerFactory();

sf.initialize(fileName);//屬性文件全名

Scheduler scheduler = sf.getScheduler();

scheduler.start();

DirectSchedulerFactory

DirectSchedulerFactory是另外的一個SchedulerFactory實現。在更多的編程方法裏創建調度器時,他很有用。他的用法不被贊成,原因有:1.它需要用戶更清楚的知道他們在做什麼。2.它不允許配置,就是說,你必須要在代碼裏配置所有的調度器屬性。

Logging

Quartz給它所有需要的日誌是使用org.apache.commons.logging框架的。Quartz沒有產生很多的日誌信息。僅有一些在初始化時關於一些jobs正在執行的問題的信息。爲了調整日誌設置,我們需要了解Jakarta Commons Logging框架,超過了本文檔討論的範圍。

11.高級(企業)特性

集羣

目前集羣僅以JDBC-Jobstore (JobStoreTX or JobStoreCMT)工作。這些特性包含load-balancing和任務fail-over(如果JobDetail的"request recovery"標誌設爲true的話)。

通過設置org.quartz.jobStore.isClustered屬性爲“true”來使用集羣。在集羣裏的每個調度器實例應該用一樣的quartz.properties文件。集羣會有如下異常:線程池大小不同,屬性org.quartz.scheduler.instanceName值不同。其實在集羣的每個節點都有一個唯一的實例ID,要達到這樣也很簡單,也不需要不同的屬性文件,只要將屬性org.quartz.scheduler.instanceId的值設置爲“AUTO”。

不要在一個分離開的機器上運行集羣,除非他們的時鐘是用時鐘同步服務同步過的。如果不熟悉怎樣同步,參考:http://www.boulder.nist.gov/timefreq/service/its.htm

其他調度器實例在用數據表時,不要觸發一個也用到這些數據表的不是集羣的調度器實例。你會得到一些沒用的數據。

JTA 事務

在第9節解釋過JobStores,JobStoreCMT允許Quartz調度一些具有很大JTA事務的操作。

通過設置“org.quartz.scheduler.wrapJobExecutionInUserTransaction”屬性爲trueJobs也能夠在一個JTA事務裏執行。有了這個設置,一個JTA事務會在jobexecute()方法調用前開始(begin),然後在調用execute()方法結束後提交(commit)。

除了在JTA事務裏Quartz自動地和job的執行掛鉤之外,當使用JobStoreCMT時也可以調用你在調度器接口裏的實現的方法,確保你在調用一個調度器上的方法之前開始了事務。你也可以直接自己做,使用UserTransaction,或者把用了調度器的代碼放在一個使用容器的SessionBean裏來管理事務。

12. Quartz 的其他特性

Plug-Ins

Quartz 提供了一個接口(org.quartz.spi.SchedulerPlugin) 來實現plugging-in 的功能。

裝配給QuartzPlugins能提供不同的有用的功能。在org.quartz.plugins包裏有詳細說明。他們提供的功能例如:調度器啓動時自動調度jobs,記錄jobtriggers事件的歷史,當JVM退出時確保調度器關閉。

可以通過配置屬性文件來使用自己實現或Quartz自帶的插件。

JobFactory

當一個trigger觸發時,通過一個配置到調度器上的JobFactory,與trigger相關的job就被實例化了。默認的JobFactory會在job類上調用newInstance(),你可能想要創建自己的JobFactory實現來完成一些其他的事情,如:擁有應用程序的IoC或者DI容器進程/初始化job實例。

與Scheduler.setJobFactory(fact)方法聯合起來察看org.quartz.spi.JobFactory接口,

Jobs工具

Quartz也提供一些有用的job,你能夠用這些job來發郵件或者調用EJB。我們能在org.quartz.jobs包裏找到它們。

13.配置文件裏配置項總結

設置主要調度器

屬性名

必須

類型

缺省值

org.quartz.scheduler.instanceName

no

string

'QuartzScheduler'

org.quartz.scheduler.instanceId

no

string

'NON_CLUSTERED'

org.quartz.scheduler.threadName

no

string

instanceName + '_QuartzSchedulerThread'

org.quartz.scheduler.idleWaitTime

no

long

30000

org.quartz.scheduler.dbFailureRetryInterval

no

long

15000

org.quartz.scheduler.classLoadHelper.class

no

string (class name)

org.quartz.simpl.CascadingClassLoadHelper

org.quartz.context.key.SOME_KEY

no

string

none

org.quartz.scheduler.userTransactionURL

no

string (url)

'java:comp/UserTransaction'

org.quartz.scheduler.wrapJobExecutionInUserTransaction

no

booelan

false

org.quartz.scheduler.jobFactory.class

no

string (class name)

org.quartz.simpl.SimpleJobFactory

org.quartz.scheduler.instanceName 
任意的String,對於調度器自己並沒有意義。但是當多個調度器實例用在一個程序裏時,他就可以用來爲客戶端代碼區別每個調度器。如果你用集羣這個特性,你必須爲在集羣裏的每個實例用一樣的名字,實現邏輯上的一樣的調度器。

org.quartz.scheduler.instanceId 
任意的String,如果在一個集羣裏多個實例是一個邏輯上一樣的調度器時,每個實例的這項屬性必須唯一。你可以設置這項爲“AUTO”從而自動收集ID

org.quartz.scheduler.idleWaitTime 
當調度器空閒時,在再次查詢可用triggers之前,調度器將要等等待的毫秒數。正常情況下,我們不調整這個參數,除非我們用XA事務,或者在立即觸發trigger時結果延誤了。

org.quartz.scheduler.classLoadHelper.class 
不需要更改。

org.quartz.context.key.SOME_KEY 

設置org.quartz.context.key.MyKey = MyValue等價於scheduler.getContext().put("MyKey", "MyValue")

org.quartz.scheduler.userTransactionURL 
是一個JNDI URL,Quartz用它來定位應用服務器的UserTransaction管理器。Websphere用戶可能需要設置它爲“jta/usertransaction”。在Quartz配置用到JobStoreCMT時並且屬性org.quartz.scheduler.wrapJobExecutionInUserTransaction設置爲true纔有用。

org.quartz.scheduler.wrapJobExecutionInUserTransaction 
設置這項爲true使我們在調用jobexecute()之前能夠開始一個UserTransaction。在jobexecute()完成之後,事務將會提交,並且,JobDataMap也更新了(是有狀態的job)。

設置線程池

屬性名

必須

類型

缺省值

org.quartz.threadPool.class

yes

string (clas name)

null

org.quartz.threadPool.threadCount

yes

int

-1

org.quartz.threadPool.threadPriority

no

int

Thread.NORM_PRIORITY (5)

org.quartz.threadPool.makeThreadsDaemons

no

boolean

false

org.quartz.threadPool.threadsInheritGroupOfInitializingThread

no

boolean

true

org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread

no

boolean

false

org.quartz.threadPool.class 
通常使用org.quartz.simpl.SimpleThreadPool

org.quartz.threadPool.threadPriority 
在 Thread.MIN_PRIORITY (1) 和Thread.MAX_PRIORITY (10)之間

org.quartz.threadPool.makeThreadsDaemons、org.quartz.threadPool.threadsInheritGroupOfInitializingThread 和org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread 三個屬性是指定的SimpleThreadPool的屬性。

如果用自己實現的線程池,可如下配置:

org.quartz.threadPool.class = com.mycompany.goo.FooThreadPool

org.quartz.threadPool.somePropOfFooThreadPool = someValue

設置全局監聽器

全局監聽器要有一個無參數的構造器,它的屬性是通過反射設置的,僅支持簡單數據和String

Trigger監聽器:

org.quartz.triggerListener.NAME.class = com.foo.MyListenerClass

org.quartz.triggerListener.NAME.propName = propValue

org.quartz.triggerListener.NAME.prop2Name = prop2Value

job監聽器:

org.quartz.jobListener.NAME.class = com.foo.MyListenerClass

org.quartz.jobListener.NAME.propName = propValue

org.quartz.jobListener.NAME.prop2Name = prop2Value

設置Plugins

配置自己的插件(和全局監聽器差不多):

org.quartz.plugin.NAME.class = com.foo.MyPluginClass

org.quartz.plugin.NAME.propName = propValue

org.quartz.plugin.NAME.prop2Name = prop2Value

也可以配置Quartz實現的插件:

1.trigger歷史日誌記錄插件(屬性配置中的{數字}參考JavaDoc):

org.quartz.plugin.triggHistory.class=org.quartz.plugins.history.LoggingTriggerHistoryPlugin

org.quartz.plugin.triggHistory.triggerFiredMessage=

Trigger {1}.{0} fired job {6}.{5} at:{4, date, HH:mm:ss MM/dd/yyyy}

org.quartz.plugin.triggHistory.triggerCompleteMessage =

Trigger {1}.{0} completed firing job {6}.{5} at {4, date, HH:mm:ss MM/dd/yyyy}

2.XML文件中初始化job插件(屬性配置中的文件名是加載jobs用到的xml文件,這個文件必須在classPath裏):

org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin

org.quartz.plugin.jobInitializer.fileName =data/my_job_data.xml

org.quartz.plugin.jobInitializer.overWriteExistingJobs = false

org.quartz.plugin.jobInitializer.failOnFileNotFound = true

在上例中,JobInitializationPlugin只支持一個xml文件的初始化,Quartz還提供多個xml文件的初始化,用JobInitializationPluginMultiple,文件名用“,”隔開。

含有多個Jobs的一個xml文件的一個例子:

<?xml version='1.0' encoding='utf-8'?>

<quartz xmlns="http://www.opensymphony.com/quartz/JobSchedulingData" 

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.opensymphony.com/quartz/JobSchedulingData 

http://www.opensymphony.com/quartz/xml/job_scheduling_data_1_5.xsd"

version="1.5">

  

  <calendar class-name="org.quartz.impl.calendar.HolidayCalendar" replace="true">

    <name>holidayCalendar</name>

    <description>HolidayCalendar</description>

    <base-calendar class-name="org.quartz.impl.calendar.WeeklyCalendar">

      <name>weeklyCalendar</name>

      <description>WeeklyCalendar</description>

      <base-calendar class-name="org.quartz.impl.calendar.AnnualCalendar">

        <name>annualCalendar</name>

        <description>AnnualCalendar</description>

      </base-calendar>

    </base-calendar>

  </calendar>

  

  <job>

    <job-detail>

      <name>testJob1</name>

      <group>testJobs</group>

      <description>Test Job Number 1</description>

      <job-class>personal.ruanyang.quartz.plugin.SimpleJob</job-class>

      <volatility>false</volatility>

      <durability>false</durability>

      <recover>false</recover>      

      <job-data-map allows-transient-data="true">

        <entry>

          <key>test1</key>

          <value>test1</value>

        </entry>

        <entry>

          <key>test2</key>

          <value>test2</value>

        </entry>

      </job-data-map>      

    </job-detail>

    <trigger>

      <cron>

        <name>testTrigger1</name>

        <group>testJobs</group>

        <description>Test Trigger Number 1</description>

        <job-name>testJob1</job-name>

        <job-group>testJobs</job-group>

        

        <!--

    <start-time>2003-12-17 2:15:00 pm</start-time>      

        <end-time>2013-12-17 2:15:00 pm</end-time>      

-->

        <cron-expression>0/15 * * ? * *</cron-expression>

        <!-- every 15 seconds... -->

      </cron>

    </trigger>

  </job>

  

  <job>

    <job-detail>

      <name>testJob2</name>

      <group>testJobs</group>

      <description>Test Job Number 2</description>

      <job-class>personal.ruanyang.quartz.plugin.SimpleJob</job-class>

      <volatility>false</volatility>

      <durability>false</durability>

      <recover>false</recover>

    </job-detail>

    <trigger>

      <simple>

        <name>testTrigger2</name>

        <group>testJobs</group>

        <description>Test Trigger Number 2</description>

        <calendar-name>holidayCalendar</calendar-name>

        <job-name>testJob2</job-name>

        <job-group>testJobs</job-group>

        <start-time>2004-02-26T12:26:00</start-time>

        <repeat-count>10</repeat-count>

        <repeat-interval>5000</repeat-interval>

      </simple>

    </trigger>

  </job>

  

</quartz>

3.Shutdown Hook(通過捕捉JVM關閉時的事件,來關閉調度器)插件:

org.quartz.plugin.shutdownhook.class = org.quartz.plugins.management.ShutdownHookPlugin

org.quartz.plugin.shutdownhook.cleanShutdown = true

設置RMI

RMI Server Scheduler Properties

沒有必需的主要屬性,所有的都是合理的缺省的。通過RMI使用Quartz時,我們需要啓動一個配置好了的Quartz實例來通過RMI“輸出”它的服務。然後我們通過配置Quartz的調度器創建一個客戶端來“代理”它連到服務器上的工作。 

一些用戶在客戶端和服務器端經歷過類可用性(jobs classes)的問題,爲了解決這些問題,我們需要理解RMI的“codebase”和RMI的安全管理。以下資源在這方面會很有用:

RMIcodebase的精彩描敘:http://www.kedwards.com/jini/codebase.html  重要的一點要意識到,codebase是被客戶端使用的。

安全管理的快速信息:http://gethelp.devx.com/techtips/java_pro/10MinuteSolutions/10min0500.asp

最後讀來自於java API文檔的RMISecurityManager:

http://java.sun.com/j2se/1.4.2/docs/api/java/rmi/RMISecurityManager.html

屬性名

需要

缺省值

org.quartz.scheduler.rmi.export

no

false

org.quartz.scheduler.rmi.registryHost

no

'localhost'

org.quartz.scheduler.rmi.registryPort

no

1099

org.quartz.scheduler.rmi.createRegistry

no

'never'

org.quartz.scheduler.rmi.serverPort

no

random

org.quartz.scheduler.rmi.proxy

no

false

org.quartz.scheduler.rmi.export

如果我們想要Quartz調度器通過RMI輸出服務,那麼我們就把“rmi.export”標誌執爲true

org.quartz.scheduler.rmi.registryHost
能夠找到的RMI註冊的主機(常爲“localhost”)。

org.quartz.scheduler.rmi.registryPort
RMI註冊的監聽端口(常爲1099).

org.quartz.scheduler.rmi.createRegistry
設置“rmi.createRegistry” 依照我們想要Quartz怎樣創建RMI註冊。如果我們不想Quartz創建一個註冊,就可以用“false”或“never”(如已經有了一個外部的註冊在運行了)。如果我們想先要Quartz嘗試使用一個存在的註冊並且然後返回再建一個,就用“true”或者“as_needed”。如果我們想要Quartz嘗試創建一個註冊然後返回使用一個存在的,就用“always”。如果註冊被創建,它將會綁定屬性“org.quartz.scheduler.rmi.registryPort”提供的端口,“org.quartz.rmi.registryHost”應該是主機。

 org.quartz.scheduler.rmi.serverPort
Quartz調度器服務將綁定和監聽連接的端口。缺省的,RMI服務將隨機選擇一個端口。

org.quartz.scheduler.rmi.proxy
如果想要連接到遠程的調度器服務,我們就要設置“org.quartz.scheduler.rmi.proxy”爲true。然後必需指定一個主機和它註冊了的端口號。

在同一個文件裏給“org.quartz.scheduler.rmi.export”和“org.quartz.scheduler.rmi.proxy”同時設置爲true並沒有意義。如果你這樣做的話,“export”項會被忽略。如果你沒有通過RMIQuartz,給這兩項同時設置爲false當然也沒有用。

設置RAMJobStore

RAMJobStore用來在內存裏儲存調度時的信息(jobtriggercalendars)。RAMJobStore很快並且是輕量級的,但是當進程終止時所有的信息都將丟失。

通過設置“org.quartz.jobStore.class”屬性來選用RAMJobStore:

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

RAMJobStore 能夠通過下面的屬性來調整:

屬性名

需要

類型

缺省值

org.quartz.jobStore.misfireThreshold

no

int

60000

org.quartz.jobStore.misfireThreshold 
在觸發器被認爲沒有觸發之前,調度器能承受一個觸發器再次觸發的一個毫秒級數字。

設置JDBC-JobStoreTX

JobStoreTX是在每次行爲(如增加一個job)之後,通過調用commit() (或者 rollback())來管理事務。如果你在一個單機應用裏或者當在一個servlet容器裏用Quartz而且應用沒有用JTA事務時,JDBCJobStore是正確的。

JobStoreTX是通過設置“org.quartz.jobStore.class”屬性來選用的:

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

JobStoreTX能夠通過以下屬性來調整:

屬性名

必須

類型

缺省值

org.quartz.jobStore.driverDelegateClass

yes

string

null

org.quartz.jobStore.dataSource

yes

string

null

org.quartz.jobStore.tablePrefix

no

string

"QRTZ_"

org.quartz.jobStore.useProperties

no

boolean

false

org.quartz.jobStore.misfireThreshold

no

int

60000

org.quartz.jobStore.isClustered

no

boolean

false

org.quartz.jobStore.clusterCheckinInterval

no

long

15000

org.quartz.jobStore.maxMisfiresToHandleAtATime

no

int

20

org.quartz.jobStore.dontSetAutoCommitFalse

no

boolean

false

org.quartz.jobStore.selectWithLockSQL

no

string

"SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE"

org.quartz.jobStore.txIsolationLevelSerializable

no

boolean

false

org.quartz.jobStore.driverDelegateClass 

· org.quartz.impl.jdbcjobstore.StdJDBCDelegate (所有JDBC兼容的驅動) 

· org.quartz.impl.jdbcjobstore.MSSQLDelegate (Microsoft SQL Server和Sybase) 

· org.quartz.impl.jdbcjobstore.PostgreSQLDelegate 

· org.quartz.impl.jdbcjobstore.WebLogicDelegate (WebLogic驅動

· org.quartz.impl.jdbcjobstore.oracle.OracleDelegate 

· org.quartz.impl.jdbcjobstore.oracle.WebLogicOracleDelegate (用在Weblogic裏的Oracle驅動

· org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate (用在Weblogic裏的Oracle驅動

· org.quartz.impl.jdbcjobstore.CloudscapeDelegate 

· org.quartz.impl.jdbcjobstore.DB2v6Delegate 

· org.quartz.impl.jdbcjobstore.DB2v7Delegate 

· org.quartz.impl.jdbcjobstore.HSQLDBDelegate 

· org.quartz.impl.jdbcjobstore.PointbaseDelegate 

org.quartz.jobStore.misfireThreshold 
RAM

org.quartz.jobStore.clusterCheckinInterval
影響着覈查出失敗實例的速度。

org.quartz.jobStore.dontSetAutoCommitFalse
設置這個屬性爲“true”是讓Quartz不去在JDBC連接上調用setAutoCommit(false)這個函數。

org.quartz.jobStore.selectWithLockSQL
在“LOCKS”表裏選擇一行並且鎖住這行的SQL語句。缺省的語句能夠爲大部分數據庫工作。“{0}”是在運行時你配置的表前綴。

org.quartz.jobStore.txIsolationLevelSerializable
設置“true”讓Quartz(當用JobStoreTX或CMT)在JDBC連接上調用setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE)。這可以阻止數據庫在高加載或長時間的事務情況下的鎖超時。

設置JDBC-JobStoreCMT

JobStoreCMT是依賴與被用Quartz的應用管理着的事務。JTA事務必須在嘗試調度(或卸載調度)jobs/triggers之前處在進程中。這允許調度工作成爲應用加大事務的一部分。JobStoreCMT實際上需要用到兩個數據源,一個數據源要連到被應用服務器管理的事務(通過JTA),另外一個數據源的連接在全局(JTA)事務中並不參加。當應用用JTA事務(例如通過EJB Session Beans)來執行他們的工作時,JobStoreCMT是正確的。

通過設置 'org.quartz.jobStore.class'屬性來選用JobStore:

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreCMT

JobStoreCMT通過以下屬性來調整:

屬性名

必須

類型

缺省值

org.quartz.jobStore.driverDelegateClass

yes

string

null

org.quartz.jobStore.dataSource

yes

string

null

org.quartz.jobStore.nonManagedTXDataSource

yes

string

null

org.quartz.jobStore.tablePrefix

no

string

"QRTZ_"

org.quartz.jobStore.useProperties

no

boolean

false

org.quartz.jobStore.misfireThreshold

no

int

60000

org.quartz.jobStore.isClustered

no

boolean

false

org.quartz.jobStore.clusterCheckinInterval

no

long

15000

org.quartz.jobStore.maxMisfiresToHandleAtATime

no

int

20

org.quartz.jobStore.dontSetAutoCommitFalse

no

boolean

false

org.quartz.jobStore.dontSetNonManagedTXConnectionAutoCommitFalse

no

boolean

false

org.quartz.jobStore.selectWithLockSQL

no

string

"SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE"

org.quartz.jobStore.txIsolationLevelSerializable

no

boolean

false

org.quartz.jobStore.txIsolationLevelReadCommitted

no

boolean

false

org.quartz.jobStore.dataSource

對於JobStoreCMT,數據源需要包含能夠加入JTA(容器管理)事務裏的連接。這就意味着數據源將在應用服務器裏被配置和管理,並且,Quartz將通過JNDI獲得一個句柄。

org.quartz.jobStore.nonManagedTXDataSource

JobStoreCMT需要一個數據源(以上說的第二個)連到不是容器管理的事務。這個值將是定義在配置屬性文件的一個數據源名稱,這個數據源必須包含非CMT的連接,換句話說,就是Quartz直接在連接上調用commit()和rollback()。

org.quartz.jobStore.dontSetNonManagedTXConnectionAutoCommitFalse
除了它應用於非TX數據源管理,其他的和org.quartz.jobStore.dontSetAutoCommitFalse一樣

org.quartz.jobStore.txIsolationLevelReadCommitted
設置“true”,讓Quartz在沒有被管理的JDBC連接上調用setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED)。這可以阻止一些數據庫(如DB2)在高加載和長時間事務的情況下發生的鎖超時。

設置數據源

如果你用JDBC-JobStore,你將需要一個數據源(或在JobStoreCMT裏要2個)。

數據源能通過2種方法配置:

1. Quartz蒐集所有指定在quartz.properties 文件裏的屬性來創建數據源。 

2. 指定一個定位於管理數據源的應用服務器的JNDI,這樣Quartz能用它。

每個定義的數據源必須有個名字,你爲這個數據源定義的一些屬性必須包含這個名字,象下面的。數據源的“NAME”可以隨便取,只有當我們把數據源賦給JDBCJobStore時,這個名字起到標示的作用,其他情況下沒什麼用。

Quartz自己創建數據源通過以下屬性:

屬性名

必須

類型

缺省值

org.quartz.dataSource.NAME.driver

yes

String

null

org.quartz.dataSource.NAME.URL

yes

String

null

org.quartz.dataSource.NAME.user

no

String

""

org.quartz.dataSource.NAME.password

no

String

""

org.quartz.dataSource.NAME.maxConnections

no

int

10

org.quartz.dataSource.NAME.validationQuery

no

String

null

org.quartz.dataSource.NAME.validationQuery
是一個可選的SQL查詢字符串,數據源用它來覈查和替代失敗/被破壞的連接。例如,一個Oracle用戶可能選擇“select table_name from user_tables”-這是一個決不可能失敗的查詢,除非連接是壞的。

org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver

org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@10.0.1.23:1521:demodb

org.quartz.dataSource.myDS.user = myUser

org.quartz.dataSource.myDS.password = myPassword

org.quartz.dataSource.myDS.maxConnections = 30

引用應用服務器的數據源:

屬性值

必須

類型

缺省值

org.quartz.dataSource.NAME.jndiURL

yes

String

null

org.quartz.dataSource.NAME.java.naming.factory.initial

no

String

null

org.quartz.dataSource.NAME.java.naming.provider.url

no

String

null

org.quartz.dataSource.NAME.java.naming.security.principal

no

String

null

org.quartz.dataSource.NAME.java.naming.security.credentials

no

String

null

org.quartz.dataSource.NAME.java.naming.factory.initial 
JNDI上下文初始化工廠的類名。

org.quartz.dataSource.NAME.java.naming.provider.url 
連接到JNDI上下文的URL

org.quartz.dataSource.NAME.java.naming.security.principal 
連接到JNDI上下文的首要用戶。

org.quartz.dataSource.NAME.java.naming.security.credentials 
連接到JNDI上下文的用戶驗證密碼。

org.quartz.dataSource.myOtherDS.jndiURL=jdbc/myDataSource

org.quartz.dataSource.myOtherDS.java.naming.factory.initial=

com.evermind.server.rmi.RMIInitialContextFactory

org.quartz.dataSource.myOtherDS.java.naming.provider.url=ormi://localhost

org.quartz.dataSource.myOtherDS.java.naming.security.principal=admin

org.quartz.dataSource.myOtherDS.java.naming.security.credentials=123

設置集羣

集羣可以通過fail-over和load balancing功能給調度器帶來既高可靠性又可伸縮性兩大優點。

集羣目前僅能和JDBC-JobStoreJobStoreTX或JobStoreCMT)一起工作,本質上是讓集羣的每個節點共享一個數據庫來工作的。

Load-balancing是自動出現的,集羣的每個節點儘可能快地觸發job。當一個觸發器觸發時刻到了,第一個將獲取觸發器(並加鎖)的節點就是將要觸發它的節點。

Fail-over是一個節點正在執行一個或多個jobs時失敗了出現的。當一個節點失敗了,其他的節點就會在數據庫裏覈查條件和鑑別jobs,這些是節點失敗時記錄到了數據庫的。在恢復節點時,任何標記了恢復(JobDetail裏的"requests recovery"屬性)的jobs將會被再次執行,沒有標記的將會簡單地釋放掉。

通過設置“org.quartz.jobStore.isClustered”屬性來使用集羣。在集羣裏每個實例應該用一樣的quartz.properties文件。用到的異常也應該是一樣的:不同線程池大小,不同“org.quartz.scheduler.instanceName”屬性值。每個節點應該用唯一的instanceId。我們可以設置org.quartz.scheduler.instanceId的值爲“AUTO”來達到這個目的。

不要在一個分離開的機器上運行集羣,除非他們的時鐘是用時鐘同步服務同步過的。如果不熟悉怎樣同步,參考:http://www.boulder.nist.gov/timefreq/service/its.htm

其他實例在用數據表時,不要觸發一個不是集羣的也用這些數據表的實例。你會得到一些沒用的數據。

#=================================================================# Configure Main Scheduler Properties  

#=================================================================org.quartz.scheduler.instanceName = MyClusteredScheduler

org.quartz.scheduler.instanceId = AUTO

#=================================================================# Configure ThreadPool  

#=================================================================

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

org.quartz.threadPool.threadCount = 25

org.quartz.threadPool.threadPriority = 5

#=================================================================# Configure JobStore  

#=================================================================

org.quartz.jobStore.misfireThreshold = 60000

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.OracleDelegate

org.quartz.jobStore.useProperties = false

org.quartz.jobStore.dataSource = myDS

org.quartz.jobStore.tablePrefix = QRTZ_

org.quartz.jobStore.isClustered = true

org.quartz.jobStore.clusterCheckinInterval = 20000

#=================================================================

# Configure Datasources  

#=================================================================

org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver

org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@polarbear:1521:dev

org.quartz.dataSource.myDS.user = quartz

org.quartz.dataSource.myDS.password = quartz

org.quartz.dataSource.myDS.maxConnections = 5

org.quartz.dataSource.myDS.validationQuery=select 0 from dual

14.Web應用中用Quartz

初始化調度器

我們可以在Web應用中的配置文件web.xml裏設置一個QuartzServlet-QuartzInitializerServlet:

<web-app>

<servlet>

<servlet-name>QuartzInitializer</servlet-name> 

  <display-name>Quartz Initializer Servlet</display-name>

<servlet-class>

    org.quartz.ee.servlet.QuartzInitializerServlet

  </servlet-class> 

  <load-on-startup>1</load-on-startup>

  <init-param>

    <param-name>config-file</param-name>

    <param-value>/some/path/my_quartz.properties</param-value>

  </init-param>

  <init-param>

    <param-name>shutdown-on-unload</param-name>

    <param-value>true</param-value>

  </init-param>

  <init-param>

    <param-name>start-scheduler-on-load</param-name>

    <param-value>true</param-value>

  </init-param>

 </servlet> 

 <!-- other web.xml items here -->

</web-app>

說明:config-file參數值是StdSchedulerFactory用來實例化調度器的,可以把自己寫的Quartz屬性文件放在classPathWEB-INF/classes路徑下。

訪問調度器

Quartz1.5開始,QuartzInitializerServlet將自動儲存StdSchedulerFactory實例在ServletContext裏:

// 從Session中獲得ServletContext

ServletContext ctx =

  request.getSession().getServletContext();

// 從ServletContext中獲得StdSchedulerFactory

StdSchedulerFactory factory = (StdSchedulerFactory)ctx.getAttribute(

QuartzFactoryServlet.QUARTZ_FACTORY_KEY);

// 從StdSchedulerFactory中獲得Scheduler

Scheduler scheduler = factory.getScheduler();

// 啓動Scheduler

scheduler.start();

FAQ

1. 怎樣控制Job實例?

看看org.quartz.spi.JobFactory 和 the org.quartz.Scheduler.setJobFactory(..) 方法。

2. 在一個job完成之後,我怎樣阻止它被刪掉?

設置JobDetail.setDurability(true)-job是一個“孤兒”(沒有trigger引用這個job)時,這將指示Quartz不要刪掉它。

3. 怎樣阻止job並行觸發?

使job類實現StatefulJob接口而不是job接口。察看StatefulJob 的JavaDoc

4. 怎樣使一個正在執行的job停下來?

看看org.quartz.InterruptableJob接口和Scheduler.interrupt(String, String)方法。

5. 怎樣使Jobs的執行串聯起來?

有兩個方法:

一、 用監聽器

二、用JobDataMap

6. 怎樣提高JDBC-JobStore的性能?

除了硬件的提高外,我們可以給我們建的Quartz表建索引:

create index idx_qrtz_t_next_fire_time on qrtz_triggers(NEXT_FIRE_TIME);

create index idx_qrtz_t_state on qrtz_triggers(TRIGGER_STATE);

create index idx_qrtz_t_nf_st on qrtz_triggers(TRIGGER_STATE,NEXT_FIRE_TIME);

create index idx_qrtz_ft_trig_name on qrtz_fired_triggers(TRIGGER_NAME);

create index idx_qrtz_ft_trig_group on qrtz_fired_triggers(TRIGGER_GROUP);

create index idx_qrtz_ft_trig_name on qrtz_fired_triggers(TRIGGER_NAME);

create index idx_qrtz_ft_trig_n_g on 

qrtz_fired_triggers(TRIGGER_NAME,TRIGGER_GROUP);

create index idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers(INSTANCE_NAME);

create index idx_qrtz_ft_job_name on qrtz_fired_triggers(JOB_NAME);

create index idx_qrtz_ft_job_group on qrtz_fired_triggers(JOB_GROUP);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章