作業調度框架 Quartz 學習筆記(五) -- 錯過的任務怎麼辦?

不知道大家在用Quartz的時候 有沒有遇到這樣一種情況:

觸發器設定每3秒鐘觸發一次 ,但是工作需要10秒鐘的執行時間.因此,在一次任務結束執行前,觸發器已經錯失觸發

當這種情況下我們怎麼處理呢,讓我們一起學習一下......

還是先貼代碼:

job類:StatefulDumbJob.java

[java] view plaincopy
  1. import java.text.SimpleDateFormat;  
  2. import java.util.Calendar;  
  3. import org.quartz.DisallowConcurrentExecution;  
  4. import org.quartz.Job;  
  5. import org.quartz.JobDataMap;  
  6. import org.quartz.JobExecutionContext;  
  7. import org.quartz.JobExecutionException;  
  8. import org.quartz.PersistJobDataAfterExecution;  
  9.   
  10. @PersistJobDataAfterExecution  
  11. @DisallowConcurrentExecution  
  12. public class StatefulDumbJob implements Job {  
  13.   
  14.     // 靜態常量,作爲任務在調用間,保持數據的鍵(key)  
  15.     // NUM_EXECUTIONS,保存的計數每次遞增1  
  16.     // EXECUTION_DELAY,任務在執行時,中間睡眠的時間。本例中睡眠時間過長導致了錯失觸發  
  17.     public static final String NUM_EXECUTIONS = "NumExecutions";  
  18.     public static final String EXECUTION_DELAY = "ExecutionDelay";  
  19.   
  20.     @Override  
  21.     public void execute(JobExecutionContext context)  
  22.             throws JobExecutionException {  
  23.   
  24.         // 任務執行的時間  
  25.         SimpleDateFormat dateFormat = new SimpleDateFormat(  
  26.                 "yyyy-MM-dd HH:mm:ss");  
  27.         String jobRunTime = dateFormat.format(Calendar.getInstance().getTime());  
  28.   
  29.         System.err.println("---" + context.getJobDetail().getKey().getName() + " 在  : ["  
  30.                 + jobRunTime + "] 執行了!!");  
  31.   
  32.         // 任務執行計數 累加  
  33.         JobDataMap map = context.getJobDetail().getJobDataMap();  
  34.         int executeCount = 0;  
  35.         if (map.containsKey(NUM_EXECUTIONS)) {  
  36.             executeCount = map.getInt(NUM_EXECUTIONS);  
  37.         }  
  38.         executeCount++;  
  39.         map.put(NUM_EXECUTIONS, executeCount);  
  40.   
  41.         // 睡眠時間: 由調度類重新設置值 ,本例爲 睡眠10s  
  42.         long delay = 5000l;  
  43.         if (map.containsKey(EXECUTION_DELAY)) {  
  44.             delay = map.getLong(EXECUTION_DELAY);  
  45.         }  
  46.   
  47.         try {  
  48.             Thread.sleep(delay);  
  49.         } catch (Exception ignore) {  
  50.         }  
  51.   
  52.         // 睡眠醒來後,打印任務執行結束的信息  
  53.         System.err.println("  -" + context.getJobDetail().getKey().getName()  
  54.                 + " 完成次數  : " + executeCount );  
  55.     }  
  56. }  

調度類: MisfireExample.java

[java] view plaincopy
  1. import static org.quartz.DateBuilder.nextGivenSecondDate;  
  2. import static org.quartz.JobBuilder.newJob;  
  3. import static org.quartz.SimpleScheduleBuilder.simpleSchedule;  
  4. import static org.quartz.TriggerBuilder.newTrigger;  
  5.   
  6. import java.text.SimpleDateFormat;  
  7. import java.util.Date;  
  8.   
  9. import org.quartz.JobDetail;  
  10. import org.quartz.Scheduler;  
  11. import org.quartz.SchedulerFactory;  
  12. import org.quartz.SchedulerMetaData;  
  13. import org.quartz.SimpleTrigger;  
  14. import org.quartz.impl.StdSchedulerFactory;  
  15.   
  16. public class MisfireExample {  
  17.   
  18.     public static void main(String[] args) throws Exception {  
  19.         MisfireExample example = new MisfireExample();  
  20.         example.run();  
  21.     }  
  22.   
  23.     public void run() throws Exception {  
  24.         // 任務執行的時間 格式化  
  25.         SimpleDateFormat dateFormat = new SimpleDateFormat(  
  26.                 "yyyy-MM-dd HH:mm:ss");  
  27.   
  28.         SchedulerFactory sf = new StdSchedulerFactory();  
  29.         Scheduler sched = sf.getScheduler();  
  30.         System.out.println("--------------- 初始化 -------------------");  
  31.   
  32.         // 下一個第15秒  
  33.         Date startTime = nextGivenSecondDate(null15);  
  34.   
  35.         // statefulJob1 每3s運行一次,但它會延遲10s  
  36.         JobDetail job = newJob(StatefulDumbJob.class)  
  37.                 .withIdentity("statefulJob1""group1")  
  38.                 .usingJobData(StatefulDumbJob.EXECUTION_DELAY, 10000L) // 設置參數:睡眠時間 10s  
  39.                 .build();  
  40.         SimpleTrigger trigger = newTrigger()  
  41.                 .withIdentity("trigger1""group1")  
  42.                 .startAt(startTime)  
  43.                 .withSchedule(  
  44.                         simpleSchedule().withIntervalInSeconds(3)  
  45.                                 .repeatForever()).build();  
  46.         Date ft = sched.scheduleJob(job, trigger);  
  47.         System.out.println(job.getKey().getName() + " 將在: " + dateFormat.format(ft)  
  48.                 + "  時運行.並且重複: " + trigger.getRepeatCount() + " 次, 每次間隔 "  
  49.                 + trigger.getRepeatInterval() / 1000 + " 秒");  
  50.   
  51.         // statefulJob2 將每3s運行一次 , 但它將延遲10s , 然後不斷的迭代  
  52.         job = newJob(StatefulDumbJob.class)  
  53.                 .withIdentity("statefulJob2""group1")  
  54.                 .usingJobData(StatefulDumbJob.EXECUTION_DELAY, 10000L)// 設置參數:睡眠時間 10s  
  55.                 .build();  
  56.         trigger = newTrigger()  
  57.                 .withIdentity("trigger2""group1")  
  58.                 .startAt(startTime)  
  59.                 .withSchedule(  
  60.                         simpleSchedule()  
  61.                                 .withIntervalInSeconds(3)  
  62.                                 .repeatForever()  
  63.                                  // 設置錯失觸發後的調度策略   
  64.                                 .withMisfireHandlingInstructionNowWithRemainingCount()  
  65.                                 )   
  66.                 .build();  
  67.   
  68.         ft = sched.scheduleJob(job, trigger);  
  69.         System.out.println(job.getKey().getName() + " 將在: " + dateFormat.format(ft)  
  70.                 + "  時運行.並且重複: " + trigger.getRepeatCount() + " 次, 每次間隔 "  
  71.                 + trigger.getRepeatInterval() / 1000 + " 秒");  
  72.   
  73.         System.out.println("------- 開始調度 (調用.start()方法) ----------------");  
  74.         sched.start();  
  75.   
  76.         // 給任務留時間運行  
  77.         Thread.sleep(600L * 1000L);  
  78.   
  79.         sched.shutdown(true);  
  80.         System.out.println("------- 調度已關閉 ---------------------");  
  81.   
  82.         // 顯示一下 已經執行的任務信息  
  83.         SchedulerMetaData metaData = sched.getMetaData();  
  84.         System.out.println("~~~~~~~~~~  執行了 "  
  85.                 + metaData.getNumberOfJobsExecuted() + " 個 jobs.");  
  86.     }  
  87. }  


-------------------------------------我是分割線----------------------------------------------------------

先說明 一個詞 : misfire -- 指的是 錯過了觸發時間

你會注意到2個觸發器具有相同的時間安排,相同的任務

觸發器設定每3秒鐘觸發一次,但是工作需要10秒鐘的執行時間

因此,在一次任務結束執行前,觸發器已經錯失觸發(除非錯失觸發時限被設置爲超過7)

 

其中第二個任務設置自己的錯失觸發指示:.withMisfireHandlingInstructionNowWithRemainingCount()

  所以當檢測到丟失觸發時,不會立即觸發,而是忽略本次安排到下一個預定時間去觸發

 

我們的結論 :

a)       本範例中,觸發的間隔被設置爲3秒,但是由於任務體執行間睡眠10秒,導致了錯失觸發的產生。

b)       實際運行的效果,2個任務執行的間隔爲10秒。

c)        由於丟失觸發時,job2的策略是立即觸發,而job1是等待下一次機會觸發。所以job2會趕在job1的前頭,最終運行次數大於job1

 

Quartz 的 Misfire處理規則:

 調度(scheduleJob)或恢復調度(resumeTrigger,resumeJob)後不同的misfire對應的處理規則


CronTrigger

withMisfireHandlingInstructionDoNothing
——不觸發立即執行
——等待下次Cron觸發頻率到達時刻開始按照Cron頻率依次執行

withMisfireHandlingInstructionIgnoreMisfires
——以錯過的第一個頻率時間立刻開始執行
——重做錯過的所有頻率週期後
——當下一次觸發頻率發生時間大於當前時間後,再按照正常的Cron頻率依次執行

withMisfireHandlingInstructionFireAndProceed
——以當前時間爲觸發頻率立刻觸發一次執行
——然後按照Cron頻率依次執行


SimpleTrigger

withMisfireHandlingInstructionFireNow
——以當前時間爲觸發頻率立即觸發執行
——執行至FinalTIme的剩餘週期次數
——以調度或恢復調度的時刻爲基準的週期頻率,FinalTime根據剩餘次數和當前時間計算得到
——調整後的FinalTime會略大於根據starttime計算的到的FinalTime值

withMisfireHandlingInstructionIgnoreMisfires

——以錯過的第一個頻率時間立刻開始執行
——重做錯過的所有頻率週期
——當下一次觸發頻率發生時間大於當前時間以後,按照Interval的依次執行剩下的頻率
——共執行RepeatCount+1次

withMisfireHandlingInstructionNextWithExistingCount
——不觸發立即執行
——等待下次觸發頻率週期時刻,執行至FinalTime的剩餘週期次數
——以startTime爲基準計算週期頻率,並得到FinalTime
——即使中間出現pause,resume以後保持FinalTime時間不變


withMisfireHandlingInstructionNowWithExistingCount
——以當前時間爲觸發頻率立即觸發執行
——執行至FinalTIme的剩餘週期次數
——以調度或恢復調度的時刻爲基準的週期頻率,FinalTime根據剩餘次數和當前時間計算得到
——調整後的FinalTime會略大於根據starttime計算的到的FinalTime值

withMisfireHandlingInstructionNextWithRemainingCount
——不觸發立即執行
——等待下次觸發頻率週期時刻,執行至FinalTime的剩餘週期次數
——以startTime爲基準計算週期頻率,並得到FinalTime
——即使中間出現pause,resume以後保持FinalTime時間不變

withMisfireHandlingInstructionNowWithRemainingCount
——以當前時間爲觸發頻率立即觸發執行
——執行至FinalTIme的剩餘週期次數
——以調度或恢復調度的時刻爲基準的週期頻率,FinalTime根據剩餘次數和當前時間計算得到

——調整後的FinalTime會略大於根據starttime計算的到的FinalTime值

MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
——此指令導致trigger忘記原始設置的starttime和repeat-count
——觸發器的repeat-count將被設置爲剩餘的次數
——這樣會導致後面無法獲得原始設定的starttime和repeat-count值

 

另外,如果任務數超過了Quartz的線程池中的線程數時,也會發生類似的情況 兄弟們可以參考一下下面的這篇博文:

http://www.cnblogs.com/makemelaugh/archive/2012/06/17/2533105.html

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