1. 程式人生 > >quartz詳解2:quartz由淺入深

quartz詳解2:quartz由淺入深

adc 伸縮 execute path 頻繁 it168 cmt logs 展示

http://blog.itpub.net/11627468/viewspace-1763498/

一、quartz核心概念

先來看一張圖:
技術分享圖片

scheduler

任務調度器

trigger

觸發器,用於定義任務調度時間規則

job

任務,即被調度的任務

misfire

錯過的,指本來應該被執行但實際沒有被執行的任務調度

    • Job:是一個接口,只有一個方法void execute(JobExecutionContext context),開發者實現該接口定義運行任務,JobExecutionContext類提供了調度上下文的各種信息。Job運行時的信息保存在JobDataMap實例中;
    • JobDetail:Quartz在每次執行Job時,都重新創建一個Job實例,所以它不直接接受一個Job的實例,相反它接收一個Job實現類,以便運行時通過newInstance()的反射機制實例化Job。因此需要通過一個類來描述Job的實現類及其它相關的靜態信息,如Job名字、描述、關聯監聽器等信息,JobDetail承擔了這一角色。
    • Trigger:是一個類,描述觸發Job執行的時間觸發規則。主要有SimpleTrigger和CronTrigger這兩個子類。當僅需觸發一次或者以固定時間間隔周期執行,SimpleTrigger是最適合的選擇;而CronTrigger則可以通過Cron表達式定義出各種復雜時間規則的調度方案:如每早晨9:00執行,周一、周三、周五下午5:00執行等;
    • Calendar:org.quartz.Calendar和java.util.Calendar不同,它是一些日歷特定時間點的集合(可以簡單地將org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一個日歷時間點,無特殊說明後面的Calendar即指org.quartz.Calendar)。一個Trigger可以和多個Calendar關聯,以便排除或包含某些時間點。假設,我們安排每周星期一早上10:00執行任務,但是如果碰到法定的節日,任務則不執行,這時就需要在Trigger觸發機制的基礎上使用Calendar進行定點排除。
    • Scheduler
      :代表一個Quartz的獨立運行容器,Trigger和JobDetail可以註冊到Scheduler中,兩者在Scheduler中擁有各自的組及名稱,組及名稱是Scheduler查找定位容器中某一對象的依據,Trigger的組及名稱必須唯一,JobDetail的組和名稱也必須唯一(但可以和Trigger的組和名稱相同,因為它們是不同類型的)。Scheduler定義了多個接口方法,允許外部通過組及名稱訪問和控制容器中Trigger和JobDetail。
Scheduler可以將Trigger綁定到某一JobDetail中,這樣當Trigger觸發時,對應的Job就被執行。一個Job可以對應多個Trigger,但一個Trigger只能對應一個Job。可以通過SchedulerFactory創建一個Scheduler實例。Scheduler擁有一個SchedulerContext,它類似於ServletContext,保存著Scheduler上下文信息,Job和Trigger都可以訪問SchedulerContext內的信息。SchedulerContext內部通過一個Map,以鍵值對的方式維護這些上下文數據,SchedulerContext為保存和獲取數據提供了多個put()和getXxx()的方法。可以通過Scheduler# getContext()獲取對應的SchedulerContext實例;
    • ThreadPool:Scheduler使用一個線程池作為任務運行的基礎設施,任務通過共享線程池中的線程提高運行效率。



二、如何使用
關於簡單使用,可以參考quartz的example,下面鏈接是一些入門幫助。
http://blog.csdn.net/ychatou1220/article/details/5806871
http://blog.csdn.net/ychatou1220/article/details/5806914
http://blog.csdn.net/ychatou1220/article/details/5806946


三、quartz設計分析

quartz.properties文件

  Quartz有一個叫做quartz.properties的配置文件,它允許你修改框架運行時環境。缺省是使用Quartz.jar裏面的quartz.properties文件。當然,你應該創建一個quartz.properties文件的副本並且把它放入你工程的classes目錄中以便類裝載器找到它。

  一旦將Quartz.jar文件和第三方庫加到自己的工程裏面並且quartz.properties文件在工程的classes目錄中,就可以創建作業了。然而,在做這之前,我們暫且回避一下先簡短討論一下Quartz架構。

Quartz內部架構

  在規模方面,Quartz跟大多數開源框架類似。大約有300個Java類和接口,並被組織到12個包中。這可以和Apache Struts把大約325個類和接口以及組織到11個包中相比。盡管規模幾乎不會用來作為衡量框架質量的一個特性,但這裏的關鍵是quarts內含很多功能,這些功能和特性集是否成為、或者應該成為評判一個開源或非開源框架質量的因素。

Quartz調度器

  Quartz框架的核心是調度器。調度器負責管理Quartz應用運行時環境。調度器不是靠自己做所有的工作,而是依賴框架內一些非常重要的部件。Quartz不僅僅是線程和線程管理。為確保可伸縮性,Quartz采用了基於多線程的架構。

  啟動時,框架初始化一套worker線程,這套線程被調度器用來執行預定的作業。這就是Quartz怎樣能並發運行多個作業的原理。Quartz依賴一套松耦合的線程池管理部件來管理線程環境。本文中,我們會多次提到線程池管理,但Quartz裏面的每個對象是可配置的或者是可定制的。所以,例如,如果你想要插進自己線程池管理設施,我猜你一定能!

作業

  用Quartz的行話講,作業是一個執行任務的簡單Java類。任務可以是任何Java代碼。只需你實現org.quartz.Job接口並且在出現嚴重錯誤情況下拋出JobExecutionException異常即可。

  Job接口包含唯一的一個方法execute(),作業從這裏開始執行。一旦實現了Job接口和execute()方法,當Quartz確定該是作業運行的時候,它將調用你的作業。Execute()方法內就完全是你要做的事情。

作業管理和存儲

  作業一旦被調度,調度器需要記住並且跟蹤作業和它們的執行次數。如果你的作業是30分鐘後或每30秒調用,這不是很有用。事實上,作業執行需要非常準確和即時調用在被調度作業上的execute()方法。Quartz通過一個稱之為作業存儲(JobStore)的概念來做作業存儲和管理。

有效作業存儲

  Quartz提供兩種基本作業存儲類型。第一種類型叫做RAMJobStore,它利用通常的內存來持久化調度程序信息。這種作業存儲類型最容易配置、構造和運行。對許多應用來說,這種作業存儲已經足夠了。

  然而,因為調度程序信息是存儲在被分配給JVM的內存裏面,所以,當應用程序停止運行時,所有調度信息將被丟失。如果你需要在重新啟動之間持久化調度信息,則將需要第二種類型的作業存儲。

  第二種類型的作業存儲實際上提供兩種不同的實現,但兩種實現一般都稱為JDBC作業存儲。兩種JDBC作業存儲都需要JDBC驅動程序和後臺數據庫來持久化調度程序信息。這兩種類型的不同在於你是否想要控制數據庫事務或這釋放控制給應用服務器例如BEA‘s WebLogic或Jboss。(這類似於J2EE領域中,Bean管理的事務和和容器管理事務之間的區別)這兩種JDBC作業存儲是:

· JobStoreTX:當你想要控制事務或工作在非應用服務器環境中是使用

· JobStoreCMT:當你工作在應用服務器環境中和想要容器控制事務時使用。

  JDBC作業存儲為需要調度程序維護調度信息的用戶而設計。

作業和觸發器

  Quartz設計者做了一個設計選擇來從調度分離開作業。Quartz中的觸發器用來告訴調度程序作業什麽時候觸發。框架提供了一把觸發器類型,但兩個最常用的是SimpleTrigger和CronTrigger。SimpleTrigger為需要簡單打火調度而設計。

  典型地,如果你需要在給定的時間和重復次數或者兩次打火之間等待的秒數打火一個作業,那麽SimpleTrigger適合你。另一方面,如果你有許多復雜的作業調度,那麽或許需要CronTrigger。

  CronTrigger是基於Calendar-like調度的。當你需要在除星期六和星期天外的每天上午10點半執行作業時,那麽應該使用CronTrigger。正如它的名字所暗示的那樣,CronTrigger是基於Unix克隆表達式的。

  作為一個例子,下面的Quartz克隆表達式將在星期一到星期五的每天上午10點15分執行一個作業。

0 15 10 ? * MON-FRI

下面的表達式

0 15 10 ? * 6L 2002-2005

  將在2002年到2005年的每個月的最後一個星期五上午10點15分執行作業。你不可能用SimpleTrigger來做這些事情。你可以用兩者之中的任何一個,但哪個跟合適則取決於你的調度需要。

錯過觸發(misfire)

trigger還有一個重要的屬性misfire;如果scheduler關閉了,或者Quartz線程池中沒有可用的線程來執行job,此時持久性的trigger就會錯過(miss)其觸發時間,即錯過觸發(misfire)。不同類型的trigger,有不同的misfire機制。它們默認都使用“智能機制(smart policy)”,即根據trigger的類型和配置動態調整行為。當scheduler啟動的時候,查詢所有錯過觸發(misfire)的持久性trigger。然後根據它們各自的misfire機制更新trigger的信息。當你在項目中使用Quartz時,你應該對各種類型的trigger的misfire機制都比較熟悉,這些misfire機制在JavaDoc中有說明。關於misfire機制的細節,會在講到具體的trigger時作介紹。

調度一個作業

  讓我們通過看一個例子來進入實際討論。現假定你管理一個部門,無論何時候客戶在它的FTP服務器上存儲一個文件,都得用電子郵件通知它。我們的作業將用FTP登陸到遠程服務器並下載所有找到的文件。

  然後,它將發送一封含有找到和下載的文件數量的電子郵件。這個作業很容易就幫助人們整天從手工執行這個任務中解脫出來,甚至連晚上都無須考慮。我們可以設置作業循環不斷地每60秒檢查一次,而且工作在7×24模式下。這就是Quartz框架完全的用途。

  首先創建一個Job類,將執行FTP和Email邏輯。下例展示了Quartz的Job類,它實現了org.quartz.Job接口。

用調度器調用作業

  首先創建一個作業,但為使作業能被調度器調用,你得向調度程序說明你的作業的調用時間和頻率。這個事情由與作業相關的觸發器來完成。因為我們僅僅對大約每60秒循環調用作業感興趣,所以打算使用SimpleTrigger。

  作業和觸發器通過Quartz調度器接口而被調度。我們需要從調度器工廠類取得一個調度器的實例。最容易的辦法是調用StdSchedulerFactory這個類上的靜態方法getDefaultScheduler()。

  使用Quartz框架,你需要調用start()方法來啟動調度器。例3的代碼遵循了大多數Quartz應用的一般模式:創建一個或多個作業,創建和設置觸發器,用調度器調度作業和觸發器,啟動調度器。

編程調度同聲明性調度

  我們通過編程的方法調度我們的ScanFTPSiteJob作業。就是說,我們用Java代碼來設置作業和觸發器。Quartz框架也支持在xml文件裏面申明性的設置作業調度。申明性方法允許我們更快速地修改哪個作業什麽時候被執行。

  Quartz框架有一個插件,這個插件負責讀取xml配置文件。xml配置文件包含了關於啟動Quartz應用的作業和觸發器信息。所有xml文件中的作業連同相關的觸發器都被加進調度器。你仍然需要編寫作業類,但配置那些作業類的調度器則非常動態化。你可以將xml文件中的元素跟例3代碼作個比較,它們從概念上來看是相同的。使用申明性方法的好處是維護變得極其簡單,只需改變xml配置文件和重新啟動Quartz應用即可。無須修改代碼,無須重新編譯,無須重新部署。

有狀態和無狀態作業

  作業到是無狀態的。這意味著在兩次作業執行之間,不會去維護作業執行時JobDataMap的狀態改變。如果你需要能增、刪,改JobDataMap的值,而且能讓作業在下次執行時能看到這個狀態改變,則需要用Quartz有狀態作業。

  Quartz有狀態作業實現了org.quartz.StatefulJob接口。

  無狀態和有狀態作業的關鍵不同是有狀態作業在每次執行時只有一個實例。大多數情況下,有狀態的作業不回帶來大的問題。然而,如果你有一個需要頻繁執行的作業或者需要很長時間才能完成的作業,那麽有狀態作業可能給你帶來伸縮性問題。

監聽器和插件

  每個人都喜歡監聽和插件。今天,幾乎下載任何開源框架,你必定會發現支持這兩個概念。監聽是你創建的Java類,當關鍵事件發生時會收到框架的回調。例如,當一個作業被調度、沒有調度或觸發器終止和不再打火時,這些都可以通過設置來來通知你的監聽器。Quartz框架包含了調度器監聽、作業和觸發器監聽。你可以配置作業和觸發器監聽為全局監聽或者是特定於作業和觸發器的監聽。

  一旦你的一個具體監聽被調用,你就能使用這個技術來做一些你想要在監聽類裏面做的事情。例如,你如果想要在每次作業完成時發送一個電子郵件,你可以將這個邏輯寫進作業裏面,也可以JobListener裏面。寫進JobListener的方式強制使用松耦合有利於設計上做到更好。

  Quartz插件是一個新的功能特性,無須修改Quartz源碼便可被創建和添加進Quartz框架。他為想要擴展Quartz框架又沒有時間提交改變給Quartz開發團隊和等待新版本的開發人員而設計。如果你熟悉Struts插件的話,那麽完全可以理解Quartz插件的使用。

  與其Quartz提供一個不能滿足你需要的有限擴展點,還不如通過使用插件來擁有可修整的擴展點。

集群Quartz應用

  Quartz應用能被集群,是水平集群還是垂直集群取決於你自己的需要。集群提供以下好處:

  · 伸縮性

  · 高可用性

  · 負載均衡

  目前,Quartz只能借助關系數據庫和JDBC作業存儲支持集群。將來的版本這個制約將消失並且用RAMJobStore集群將是可能的而且將不需要數據庫的支持。

四、結構與流程分析

1、定時器的啟動
參考:http://seanhe.iteye.com/blog/691835
技術分享圖片
參考這張圖,首先quartz的加載可以有兩種方式:
方式1:通過servlet,參考:http://blog.csdn.net/ychatou1220/article/details/5806914
方式2:通過spring,例如:

點擊(此處)折疊或打開

  1. <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
  2. <property name="dataSource">
  3. <ref bean="scheduleDataSource" />
  4. </property>
  5. <property name="autoStartup" value="true" />
  6. <property name="schedulerFactoryClass" value="org.quartz.impl.StdSchedulerFactory"></property>
  7. <property name="configLocation" value="classpath:quartz.properties" />
  8. <property name="globalJobListeners">
  9. <list>
  10. <ref bean="jobListener" />
  11. </list>
  12. </property>
  13. </bean>

SpringContext在加載SchedulerFactoryBean時會去加載他的afterPropertiesSet方法。
參考:http://www.cnblogs.com/java-boy/archive/2012/12/21/2827729.html
而SchedulerFactoryBean會去與quartz的StdSchedulerFactory交互初使化配置,StdSchedulerFactory會create類QuartzScheduler
QuartzScheduler,QuartzScheduler會啟動總控制線程QuartzSchedulerThread不停的輪循。

而輪循的代碼是:

點擊(此處)折疊或打開

  1. public void run() {
  2. boolean lastAcquireFailed = false;
  3. while (!halted.get()) {
  4. ......
  5. int availThreadCount = qsRsrcs.getThreadPool().blockForAvailableThreads();
  6. if(availThreadCount > 0) {
  7. ......
  8. //調度器在trigger隊列中尋找30秒內一定數目的trigger(需要保證集群節點的系統時間一致)
  9. triggers = qsRsrcs.getJobStore().acquireNextTriggers(
  10. now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());
  11. ......
  12. //觸發trigger
  13. List<TriggerFiredResult> res = qsRsrcs.getJobStore().triggersFired(triggers);
  14. ......
  15. //釋放trigger
  16. for (int i = 0; i < triggers.size(); i++) {
  17. qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
  18. }
  19. }
  20. }

畫成圖是這樣的:
技術分享圖片

只看左邊的圖:普通線程
1、線程是否halt住了,沒有的話繼續;
2、等待,直到線程池裏有線程可處理了;
3、調度器在trigger隊列中尋找30s內一定數目的trigger批量執行,1.8.6版本是1, 2.2.1版本默認是1,但可以調整這個值。
同時查到的trigger會通過insertFiredTrigger保存到FIRED_TRIGGER表中。
4、等到時間到。
5、加鎖,批量執行>1時才加鎖,讓集群其它服務結點無法操作。然後取到需要觸發的trigger,然後再解鎖。
6、點火,在線程池執行一個線程,取觸發器、JOB,然後在這個線程執行。
7、修改數據庫狀態為正在執行。


再總結一下類的結構:
技術分享圖片


1、StdSchedulerFactory是工廠類,還有一個工廠類DirectSchedulerFactory比較簡單,而StdSchedulerFactory是可以加載各種屬性的。
屬性的加載initialize方法,Contants裏面都是參數,可以按說明在quartz.properties上加。
2、StdSchedule只是QuartzSchedule的一個包裝類,方法更清晰。
3、QuartzScheduler是整個定時任務框架工作的核心類,上面的類圖僅僅展現了QuartzScheduler中幾個核心成員。
4、QuartzSchedulerResources可以認為是存放一切配置以及通過配置初始化出來的一些資源的容器,其中包括了存儲job定義的jobStor
5、JobStore可以有多種實現,我們使用的是默認的RAMJobStore;
6、ThreadPool,還有一個非常重要的對象就是ThreadPool,這個線程池管理著執行我們定義的Job所需的所有線程。這個線程池的大小配置就是通過我上面提到過的“org.quartz.threadPool.threadCount”進行配置的。QuartzScheduler另一個重要成員就是QuartzSchedulerThread,沒有這個線程的話我們所有定義的任務都不會被觸發執行,也就是說它是Quartz後臺的“守護線程”,它不斷的去查找合適的job並觸發這些Job執行。
7、QuartzSchedulerThread主要業務邏輯在上面已經講了。

2、觸發點火

點擊(此處)折疊或打開

  1. if(goAhead) {
  2. try {
  3. //觸發trigger
  4. List<TriggerFiredResult> res = qsRsrcs.getJobStore().triggersFired(triggers);
  5. if(res != null)
  6. bndles = res;
  7. } catch (SchedulerException se) {

首先要分析一下qsRsrcs.getJobStore(),是JobStoreSupport還是RAMJobStore。JobStoreSupport是數據庫方式存Job,RAMJobStore是通過內存的方式存Job。數據庫比內存訪問要慢,但是數據不會因為服務重啟而丟失。JobStoreSupport的子類分為JobStoreTX和JobStoreCMT。JobStoreTX事務自己管理、JobStoreCMT事務交由容器管理。
技術分享圖片
在quartz.properties裏配置,即可以在Factory裏被註入。
quartz.properteis:

點擊(此處)折疊或打開

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

StdSchedulerFactory的instantiate方法中:

點擊(此處)折疊或打開

  1. // Get JobStore Properties
  2. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  3. String jsClass = cfg.getStringProperty(PROP_JOB_STORE_CLASS,
  4. RAMJobStore.class.getName());
  5. if (jsClass == null) {
  6. initException = new SchedulerException(
  7. "JobStore class not specified. ");
  8. throw initException;
  9. }
  10. try {
  11. js = (JobStore) loadHelper.loadClass(jsClass).newInstance();
  12. } catch (Exception e) {
  13. initException = new SchedulerException("JobStore class ‘" + jsClass
  14. + "‘ could not be instantiated.", e);
  15. throw initException;
  16. }
  17. SchedulerDetailsSetter.setDetails(js, schedName, schedInstId);

triggersFired的邏輯:
triggersFired後,會循環處理每個trigger。
2.selectTriggerState先查出狀態,只有可運行狀態才是可執行的。
3.selectJobDetail查出JOB的詳情。
4.修改trigger的信息為正在執行。1.8.6版本是先刪後增。
5.修改FIRED_TRIGGER表中信息為正在執行。
6.修改trigger信息,修改下一次觸發時間。

技術分享圖片

查看QuartzScheduleThread的線程執行時,可以看到執行的結果是保存在TriggerFiredBundle類裏的。

點擊(此處)折疊或打開

  1. ....
  2. List<TriggerFiredResult> res = qsRsrcs.getJobStore().triggersFired(triggers);
  3. ......
  4. for(int i = 0; i < bndles.size(); i++){
  5. TriggerFiredResult result = bndles.get(i);
  6. TriggerFiredBundle bndle = result.getTriggerFiredBundle();
  7. .......
  8. JobRunShell shell = null;
  9. try {
  10. shell = qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle);
  11. shell.initialize(qs);
  12. }



關於trigger的分類OperableTrigger接口的實現有SimpleTriggerImpl、CronTriggerImpl、CalendarIntervalTriggerImpl等。
註:1.8.6版本中CronTrigger,SimpleTrigger類繼承Trigger類
技術分享圖片

2.2.1版本把CronTrigger,SimpleTrigger都作為接口,實現類為CronTriggerImpl和SimpleTriggerImpl
技術分享圖片
接口的方式更解耦,更易擴展。
技術分享圖片




3、在線程中運行任務
技術分享圖片
參考:http://blog.csdn.net/cutesource/article/details/4965520
getSchedule只是啟動了QuartzSchedulerThread線程,開關未打開。
start()才是打開QuartzSchedulerThread的開關,真正開始線程輪循。
當總線程QuartzSchedulerThread處理完了數據庫對trigger操作後,就開始把任務放到線程中執行了。

點擊(此處)折疊或打開

  1. for (int i = 0; i < bndles.size(); i++) {
  2. TriggerFiredResult result = bndles.get(i);
  3. TriggerFiredBundle bndle = result.getTriggerFiredBundle();
  4. Exception exception = result.getException();
  5. ........................
  6. JobRunShell shell = null;
  7. try {
  8. shell = qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle);
  9. shell.initialize(qs);
  10. } catch (SchedulerException se) {
  11. qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
  12. continue;
  13. }
  14. if (qsRsrcs.getThreadPool().runInThread(shell) == false) {
  15. // this case should never happen, as it is indicative of the
  16. // scheduler being shutdown or a bug in the thread pool or
  17. // a thread pool being used concurrently - which the docs
  18. // say not to do...
  19. getLog().error("ThreadPool.runInThread() return false!");
  20. qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
  21. }
  22. }

技術分享圖片
shell = qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle); //這是獲取一個執行的shell,並實現runnable接口。
shell.initialize(qs); //初使化shell並建new Job實例化Job接口。

點擊(此處)折疊或打開

  1. JobDetail jobDetail = bundle.getJobDetail();
  2. Class<? extends Job> jobClass = jobDetail.getJobClass();
  3. 。。。。。。
  4. return jobClass.newInstance();

qsRsrcs.getThreadPool().runInThread(shell) //這是把shell真正放到線程池中的一個線程上去運行。
總結一下類圖結構:
技術分享圖片
1、JobRunShell是工作線程執行的核心,它實現了Runnable接口。放入到quartz啟動時就創建的線程池中的線程上運行。
2、JobExecutionContextImpl是包含了執行環境需要的變量。由JobRunShell在初使化時創建。
3、線程池SimpleThreadPool在quartz啟動時初使化、線程池中的工作線程WorkThread在第一調用initialize方法時創建。
線程池維護3個鏈表workers、availWorkers、busyWorkers。創建後的線程放入到availWorkers、執行時放入到busyWorkers、執行完後又放回到availWorkers.


4、trigger的狀態變化。
參考:http://www.cnblogs.com/davidwang456/p/4205237.html
最後總結下trigger的狀態:
技術分享圖片


workThread線程執行前,當fired_trigger表由ACQUIRED狀態修改為EXECUTING狀態時,trigger表由ACQUIRED狀態變為WAITING狀態。
技術分享圖片

由於為任務執行完成後,trigger才回到WAITING狀態,重新被獲取。
所以如果每隔10秒鐘執行任務,一個任務要執行8秒鐘,則同一時間只有一個線程執行。
如果每隔5秒鐘執行任務,一個任務要執行8秒鐘,則需要2個線程。
如果每隔M秒鐘執行任務,一個任務要執行N秒鐘,則需要N/M個線程。



五、quartz關鍵點分析
1、batchTriggerAcquisitionMaxCount的使用

通過測試,如果是批量執行的話,時間精度是沒有控制的。

job1每5,15,25秒執行

job2每6,16,26秒執行

job3每7,17,27秒執行

job4每8,18,28秒執行

代碼是:

 // job 1 will run every 5,15,25 second    JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1").build();    CronTrigger trigger = newTrigger().withIdentity("trigger1", "group1").withSchedule(cronSchedule("5,15,25 * * * * ?"))        .build();    Date ft = sched.scheduleJob(job, trigger);    log.info(job.getKey() + " has been scheduled to run at: " + ft + " and repeat based on expression: "             + trigger.getCronExpression()); // job 2 will run every 6,16,26 second    JobDetail job2 = newJob(SimpleJob.class).withIdentity("job2", "group1").build();    CronTrigger trigger2 = newTrigger().withIdentity("trigger2", "group1").withSchedule(cronSchedule("6,16,26 * * * * ?"))        .build();    Date ft2 = sched.scheduleJob(job2, trigger2);    log.info(job2.getKey() + " has been scheduled to run at: " + ft2 + " and repeat based on expression: "             + trigger2.getCronExpression());     // job 3 will run every 7,17,27 second    JobDetail job3 = newJob(SimpleJob.class).withIdentity("job3", "group1").build();    CronTrigger trigger3 = newTrigger().withIdentity("trigger3", "group1").withSchedule(cronSchedule("7,17,27 * * * * ?"))        .build();    Date ft3 = sched.scheduleJob(job3, trigger3);    log.info(job3.getKey() + " has been scheduled to run at: " + ft3 + " and repeat based on expression: "             + trigger3.getCronExpression());     // job 4 will run every 8,18,28 second    JobDetail job4 = newJob(SimpleJob.class).withIdentity("job4", "group1").build();    CronTrigger trigger4 = newTrigger().withIdentity("trigger4", "group1").withSchedule(cronSchedule("8,18,28 * * * * ?"))        .build();    Date ft4 = sched.scheduleJob(job4, trigger4);    log.info(job4.getKey() + " has been scheduled to run at: " + ft4 + " and repeat based on expression: "             + trigger4.getCronExpression()); 

quartz.properties增加批量處理的配置及數據庫的配置:

#批量選triggerorg.quartz.scheduler.batchTriggerAcquisitionMaxCount: 5 

增加批量配置的測試的結果是5,15,25秒時每個任務1,2,3,4都執行了:

[INFO] 21 八月 10:50:05.118 上午 DefaultQuartzScheduler_Worker-3 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job1 executing at Fri Aug 21 10:50:05 CST 2015[INFO] 21 八月 10:50:05.119 上午 DefaultQuartzScheduler_Worker-6 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job4 executing at Fri Aug 21 10:50:05 CST 2015[INFO] 21 八月 10:50:05.119 上午 DefaultQuartzScheduler_Worker-5 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job3 executing at Fri Aug 21 10:50:05 CST 2015[INFO] 21 八月 10:50:05.118 上午 DefaultQuartzScheduler_Worker-4 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job2 executing at Fri Aug 21 10:50:05 CST 2015[INFO] 21 八月 10:50:15.114 上午 DefaultQuartzScheduler_Worker-7 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job1 executing at Fri Aug 21 10:50:15 CST 2015[INFO] 21 八月 10:50:15.115 上午 DefaultQuartzScheduler_Worker-9 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job3 executing at Fri Aug 21 10:50:15 CST 2015[INFO] 21 八月 10:50:15.116 上午 DefaultQuartzScheduler_Worker-10 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job4 executing at Fri Aug 21 10:50:15 CST 2015[INFO] 21 八月 10:50:15.115 上午 DefaultQuartzScheduler_Worker-8 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job2 executing at Fri Aug 21 10:50:15 CST 2015[INFO] 21 八月 10:50:25.107 上午 DefaultQuartzScheduler_Worker-2 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job1 executing at Fri Aug 21 10:50:25 CST 2015[INFO] 21 八月 10:50:25.107 上午 DefaultQuartzScheduler_Worker-1 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job2 executing at Fri Aug 21 10:50:25 CST 2015[INFO] 21 八月 10:50:25.108 上午 DefaultQuartzScheduler_Worker-3 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job3 executing at Fri Aug 21 10:50:25 CST 2015[INFO] 21 八月 10:50:25.108 上午 DefaultQuartzScheduler_Worker-4 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job4 executing at Fri Aug 21 10:50:25 CST 2015 
#批量選triggerorg.quartz.scheduler.batchTriggerAcquisitionMaxCount: 5 

結果是分別執行,精確到秒的。

[INFO] 21 八月 10:59:05.040 上午 DefaultQuartzScheduler_Worker-5 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job1 executing at Fri Aug 21 10:59:05 CST 2015[INFO] 21 八月 10:59:06.041 上午 DefaultQuartzScheduler_Worker-6 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job2 executing at Fri Aug 21 10:59:06 CST 2015[INFO] 21 八月 10:59:07.041 上午 DefaultQuartzScheduler_Worker-7 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job3 executing at Fri Aug 21 10:59:07 CST 2015[INFO] 21 八月 10:59:08.028 上午 DefaultQuartzScheduler_Worker-8 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job4 executing at Fri Aug 21 10:59:08 CST 2015[INFO] 21 八月 10:59:15.031 上午 DefaultQuartzScheduler_Worker-9 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job1 executing at Fri Aug 21 10:59:15 CST 2015[INFO] 21 八月 10:59:16.044 上午 DefaultQuartzScheduler_Worker-10 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job2 executing at Fri Aug 21 10:59:16 CST 2015[INFO] 21 八月 10:59:17.042 上午 DefaultQuartzScheduler_Worker-1 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job3 executing at Fri Aug 21 10:59:17 CST 2015[INFO] 21 八月 10:59:18.040 上午 DefaultQuartzScheduler_Worker-2 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job4 executing at Fri Aug 21 10:59:18 CST 2015[INFO] 21 八月 10:59:25.042 上午 DefaultQuartzScheduler_Worker-3 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job1 executing at Fri Aug 21 10:59:25 CST 2015[INFO] 21 八月 10:59:26.041 上午 DefaultQuartzScheduler_Worker-4 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job2 executing at Fri Aug 21 10:59:26 CST 2015[INFO] 21 八月 10:59:27.039 上午 DefaultQuartzScheduler_Worker-5 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job3 executing at Fri Aug 21 10:59:27 CST 2015[INFO] 21 八月 10:59:28.041 上午 DefaultQuartzScheduler_Worker-6 [org.quartz.examples.example16.SimpleJob]SimpleJob says: group1.job4 executing at Fri Aug 21 10:59:28 CST 2015 

所以,batchTriggerAcquisitionMaxCount這個參數是能提高性能,不管是數據庫方式還是內存集群方式,批量執行加鎖解鎖當然可以更快。但是精度會有損失。

因此適用於每一秒都有多個任務執行的情況。

比如說每秒有1000個任務同時執行,那麽可以設置開批量功能。對於差距1s內可以忽略不計的。

在生產上建設可以把可批量執行的任務放入一個集群。

把對精度、穩定性要求高的任務放入另一個集群。

quartz詳解2:quartz由淺入深