Quartz misfireThreshold屬性的意義與觸發器超時后的處理策略。
在配置quartz.properties有這么一個屬性就是misfireThreshold,用來指定調度引擎設置觸發器超時的"臨界值"。
要弄清楚觸發器超時臨界值的意義,那么就要先弄清楚什么是觸發器超時?打個比方:比如調度引擎中有5個線程,然后在某天的下午2點 有6個任務需要執行,那么由於調度引擎中只有5個線程,所以在2點的時候會有5個任務會按照之前設定的時間正常執行,有1個任務因為沒有線程資源而被延遲執行,這個就叫觸發器超時。
那么超時的時間又是如何計算的呢?還接着上面的例子說,比如一個(任務A)應該在2點的時候執行,但是在2點的時候調度引擎中的可用線程都在忙碌狀態中,或者調度引擎掛了,這都有可能發生,然后再2點05秒的時候恢復(有可用線程或者服務器重新啟動),也就是說(任務A)應該在2點執行 但現在晚了5秒鍾。那么這5秒鍾就是任務超時時間,或者叫做觸發器(Trigger)超時時間。
理解了上面的內容再來看misfireThreshold值的意義,misfireThreshold是用來設置調度引擎對觸發器超時的忍耐時間,簡單來說 假設misfireThreshold=6000(單位毫秒)。
那么它的意思說當一個觸發器超時時間如果大於misfireThreshold的值 就認為這個觸發器真正的超時(也叫Misfires)。
如果一個觸發器超時時間 小於misfireThreshold的值, 那么調度引擎則不認為觸發器超時。也就是說調度引擎可以忍受這個超時的時間。
還是 任務A 比它應該正常的執行時間晚了5秒 那么misfireThreshold的值是6秒,那么調度引擎認為這個延遲時間可以忍受,所以不算超時(Misfires),那么引擎會按照正常情況執行該任務。 但是如果 任務A 比它應該正常的執行時間晚了7秒 或者是6.5秒 只要大於misfireThreshold 那么調度引擎就會認為這個任務的觸發器超時。
這樣的話就會出現這么情況,讓我們一個一個說明,並給出例子。
第一種情況:任務一切正常,即按照我們定義的觸發器的預期時間執行,比如下午2點運行 時間間隔3秒 重復運行 5次等,這個沒啥好說的。
第二種情況:任務出現延時,延時的時間<misfireThreshold。比如一個任務正常應該在2點運行,但是調度系統忙碌2點5秒才得空運行這個任務,這樣這個任務就被耽誤了5秒鍾。
假設這個任務的觸發器定義的是 2點執行 時間間隔為1秒 執行10次。如果正常情況 任務應該在 2點00秒 ,2點01秒,2點03秒....2點10秒觸發。但這個任務在2點05秒的時候引擎在后空去執行它,這樣的話就比我們預期的時間慢了5秒。那么調度引擎是如何執行這個任務的呢?不是是應該在在2點05秒開始執行 然后一直到2點15秒呢?經過測試我發現並不是這樣的,而是調度引擎直接把慢了的那5次立即運行,然后再每隔1秒運行5次。
DEMO:
org.quartz.threadPool.threadCount = 1 org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadPriority: 5 org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore org.quartz.jobStore.misfireThreshold = 5000
為了測試出效果,我們將threadCount調度引擎中的線程數設置為1,misfireThreshold超時忍受時間設置為5秒
任務一: 假設執行時間4秒
public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println(context.getJobDetail().getKey() + new Date().toLocaleString()); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } }
任務二: 打印當前時間
public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println(context.getJobDetail().getKey() + new Date().toLocaleString()); }
測試代碼:
SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); JobDetail job = newJob(StatefulDumbJob.class) .withIdentity("statefulJob1", "group1") .build(); SimpleTrigger trigger = newTrigger() .withIdentity("trigger1", "group1") .startNow() .withSchedule(simpleSchedule()) .build(); JobDetail job2 = newJob(StatefulDumbJob2.class) .withIdentity("statefulJob2", "group1") .build(); SimpleTrigger trigger2 = newTrigger() .withIdentity("trigger2", "group1") .startNow() .endAt(futureDate(10, IntervalUnit.SECOND)) .withSchedule(simpleSchedule() .withIntervalInSeconds(1) .withRepeatCount(10) ) .build(); sched.scheduleJob(job2, trigger2); sched.start();
分別在調度引擎中添加2個任務 job 和 job2, 倆個任務都是立即執行
由於job的執行時間需要4秒並且調度引擎中的可用線程只有一個,這就會導致job2延遲觸發。我們觀察一下控制台輸出的結果。
group1.statefulJob12014-8-19 11:44:28 group1.statefulJob22014-8-19 11:44:32 group1.statefulJob22014-8-19 11:44:32 group1.statefulJob22014-8-19 11:44:32 group1.statefulJob22014-8-19 11:44:32 group1.statefulJob22014-8-19 11:44:32 group1.statefulJob22014-8-19 11:44:33 group1.statefulJob22014-8-19 11:44:34 group1.statefulJob22014-8-19 11:44:35 group1.statefulJob22014-8-19 11:44:36
可以看到job先執行在11:44:28的時候,然后4秒鍾以后執行JOB2,調度引擎會立即執行(job2) 5次,然后再每隔一秒執行一次,直到執行完定義的次數。
第三種情況:任務觸發器超時,延遲的時間>=misfireThreshold,那么調度引擎該如何處理這個任務呢?
答案是這樣的:在定義一個任務的觸發器的時候,我們可以設置它超時的處理策略,調度引擎會根據我們設置的策略來處理這個任務。
我們在定義一個任務的觸發器時 最常用的就是倆種觸發器:1、SimpleTrigger 2、CronTrigger。
1、SimpleTrigger 默認的策略是 Trigger.MISFIRE_INSTRUCTION_SMART_POLICY 官方的解釋如下:
Instructs the Scheduler that upon a mis-fire situation, the updateAfterMisfire() method will be called on the Trigger to determine the mis-fire instruction, which logic will be trigger-implementation-dependent.
大意是:指示調度引擎在MisFire的狀態下,會去調用觸發器的updateAfterMisfire的方法來確定它的超時處理策略,里面的邏輯取決於具體的實現類。
那我們在看一下updateAfterMisfire方法的說明:
If the misfire instruction is set to MISFIRE_INSTRUCTION_SMART_POLICY, then the following scheme will be used: If the Repeat Count is 0, then the instruction will be interpreted as MISFIRE_INSTRUCTION_FIRE_NOW. If the Repeat Count is REPEAT_INDEFINITELY, then the instruction will be interpreted as MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT. WARNING: using MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT with a trigger that has a non-null end-time may cause the trigger to never fire again if the end-time arrived during the misfire time span. If the Repeat Count is > 0, then the instruction will be interpreted as MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT.
大意是:
1、如果觸發器的重復執行數(Repeat Count)等於0,那么會按這個(MISFIRE_INSTRUCTION_FIRE_NOW)策略執行。
2、如果觸發器的重復執行次數是 SimpleTrigger.REPEAT_INDEFINITELY (常量值為-1,意思是重復無限次) ,那么會按照MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT策略執行。
3、如果觸發器的重復執行次數大於0,那么按照 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT執行。
既然是這樣,那就讓我們依次看一下每種處理策略都是啥意思!
1、MISFIRE_INSTRUCTION_FIRE_NOW
Instructs the
that upon a mis-fire situation, the Scheduler
wants to be fired now by SimpleTrigger
Scheduler
.
NOTE: This instruction should typically only be used for 'one-shot' (non-repeating) Triggers. If it is used on a trigger with a repeat count > 0 then it is equivalent to the instruction
. MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
指示調度引擎在MisFire的情況下,將任務(JOB)馬上執行一次。
需要注意的是 這個指令通常被用做只執行一次的Triggers,也就是沒有重復的情況(non-repeating),如果這個Triggers的被安排的執行次數大於0
那么這個執行與 (4)MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT 相同
2、
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
Instructs the Scheduler that upon a mis-fire situation, the SimpleTrigger wants to be re-scheduled to 'now' with the repeat count left as-is. This does obey the Trigger end-time however, so if 'now' is after the end-time the Trigger will not fire again.
NOTE: Use of this instruction causes the trigger to 'forget' the start-time and repeat-count that it was originally setup with (this is only an issue if you for some reason wanted to be able to tell what the original values were at some later time).
指示調度引擎重新調度該任務,repeat count保持不變,並且服從trigger定義時的endTime,如果現在的時間,如果當前時間已經晚於 end-time,那么這個觸發器將不會在被激發。
注意:這個狀態會導致觸發器忘記最初設置的 start-time 和 repeat-count,為什么這個說呢,看源代碼片段:updateAfterMisfire方法中
else if (instr == MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT) {
Date newFireTime = new Date();
if (repeatCount != 0 && repeatCount != REPEAT_INDEFINITELY) {
setRepeatCount(getRepeatCount() - getTimesTriggered());
setTimesTriggered(0);
}
if (getEndTime() != null && getEndTime().before(newFireTime)) {
setNextFireTime(null); // We are past the end time
} else {
setStartTime(newFireTime);
setNextFireTime(newFireTime);
}
getTimesTriggered的是獲取這個觸發器已經被觸發了多少次,那么用原來的次數 減掉 已經觸發的次數就是還要觸發多少次
接下來就是判斷一下觸發器是否到了結束時間,如果到了的話,觸發器就不會在被觸發。
然后就是重新設置觸發器的開始實現是 “現在” 並且立即運行。
3、MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
Instructs the Scheduler that upon a mis-fire situation, the SimpleTrigger wants to be re-scheduled to the next scheduled time after 'now' - taking into account any associated Calendar, and with the repeat count set to what it would be, if it had not missed any firings.
NOTE/WARNING: This instruction could cause the Trigger to go directly to the 'COMPLETE' state if all fire-times where missed.
這個策略跟上面的2策略一樣,唯一的區別就是設置觸發器的時間 不是“現在” 而是下一個 scheduled time。解釋起來比較費勁,舉個例子就能說清楚了。
比如一個觸發器設置的時間是 10:00 執行 時間間隔10秒 重復10次。那么當10:07秒的時候調度引擎可以執行這個觸發器的任務。那么如果是策略(2),那么任務會立即運行。
那么觸發器的觸發時間就變成了 10:07 10:17 10:27 10:37 .....
那么如果是策略(3),那么觸發器會被安排在下一個scheduled time。 也就是10:20觸發。 然后10:30 10:40 10:50。這回知道啥意思了吧。
4、MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
這個策略跟上面的策略(2)比較類似,指示調度引擎重新調度該任務,repeat count 是剩余應該執行的次數,也就是說本來這個任務應該執行10次,但是已經錯過了3次,那么這個任務就還會執行7次。
下面是這個策略的源碼,主要看紅色的地方就能看到與策略(2)的區別,這個任務的repeat count 已經減掉了錯過的次數。
} else if (instr == MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT) { Date newFireTime = new Date(); int timesMissed = computeNumTimesFiredBetween(nextFireTime, newFireTime); if (repeatCount != 0 && repeatCount != REPEAT_INDEFINITELY) { int remainingCount = getRepeatCount() - (getTimesTriggered() + timesMissed); if (remainingCount <= 0) { remainingCount = 0; } setRepeatCount(remainingCount); setTimesTriggered(0); } if (getEndTime() != null && getEndTime().before(newFireTime)) { setNextFireTime(null); // We are past the end time } else { setStartTime(newFireTime); setNextFireTime(newFireTime); } }
5、MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
Instructs the Scheduler that upon a mis-fire situation, the SimpleTrigger wants to be re-scheduled to the next scheduled time after 'now' - taking into account any associated Calendar, and with the repeat count set to what it would be, if it had not missed any firings.
NOTE/WARNING: This instruction could cause the Trigger to go directly to the 'COMPLETE' state if all fire-times where missed.
這個策略與上面的策略3比較類似,區別就是repeat count 是剩余應該執行的次數而不是全部的執行次數。比如一個任務應該在2:00執行,repeat count=5,時間間隔5秒, 但是在2:07才獲得執行的機會,那任務不會立即執行,而是按照機會在2點10秒執行。
6、MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
Instructs the Scheduler that the Trigger will never be evaluated for a misfire situation, and that the scheduler will simply try to fire it as soon as it can, and then update the Trigger as if it had fired at the proper time.
NOTE: if a trigger uses this instruction, and it has missed several of its scheduled firings, then several rapid firings may occur as the trigger attempt to catch back up to where it would have been. For example, a SimpleTrigger that fires every 15 seconds which has misfired for 5 minutes will fire 20 times once it gets the chance to fire.
這個策略是忽略所有的超時狀態,和最上面講到的 (第二種情況) 一致。
舉個例子,一個SimpleTrigger 每個15秒鍾觸發, 但是超時了5分鍾才獲得執行的機會,那么這個觸發器會被快速連續調用20次, 追上前面落下的執行次數。
2、CronTrigger 的默認策略也是Trigger.MISFIRE_INSTRUCTION_SMART_POLICY 官方解釋如下,也就是說不指定的話默認為:MISFIRE_INSTRUCTION_FIRE_ONCE_NOW。
Updates the CronTrigger's state based on the MISFIRE_INSTRUCTION_XXX that was selected when the CronTrigger was created.
If the misfire instruction is set to MISFIRE_INSTRUCTION_SMART_POLICY, then the following scheme will be used:
The instruction will be interpreted as MISFIRE_INSTRUCTION_FIRE_ONCE_NOW
1、MISFIRE_INSTRUCTION_FIRE_ONCE_NOW
Instructs the Scheduler that upon a mis-fire situation, the CronTrigger wants to be fired now by Scheduler.
這個策略指示觸發器超時后會被立即安排執行,看源碼,紅色標記的地方。也就是說不管這個觸發器是否超過結束時間(endTime) 首選執行一次,然后就按照正常的計划執行。
@Override public void updateAfterMisfire(org.quartz.Calendar cal) { int instr = getMisfireInstruction(); if(instr == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) return; if (instr == MISFIRE_INSTRUCTION_SMART_POLICY) { instr = MISFIRE_INSTRUCTION_FIRE_ONCE_NOW; } if (instr == MISFIRE_INSTRUCTION_DO_NOTHING) { Date newFireTime = getFireTimeAfter(new Date()); while (newFireTime != null && cal != null && !cal.isTimeIncluded(newFireTime.getTime())) { newFireTime = getFireTimeAfter(newFireTime); } setNextFireTime(newFireTime); } else if (instr == MISFIRE_INSTRUCTION_FIRE_ONCE_NOW) { setNextFireTime(new Date()); } }
2、MISFIRE_INSTRUCTION_DO_NOTHING
這個策略與策略(1)正好相反,它不會被立即觸發,而是獲取下一個被觸發的時間,並且如果下一個被觸發的時間超出了end-time 那么觸發器就不會被執行。
上面綠色標記的地方是源碼
補充幾個方法的說明:
1、getFireTimeAfter 返回觸發器下一次將要觸發的時間,如果在給定(參數)的時間之后,觸發器不會在被觸發,那么返回null。
Date getFireTimeAfter(Date afterTime) Returns the next time at which the Trigger will fire, after the given time. If the trigger will not fire after the given time, null will be returned.
2、isTimeIncluded 判斷給定的時間是否包含在quartz的日歷當中,因為quartz是可以自定義日歷的,設置哪些日子是節假日什么的。
boolean isTimeIncluded(long timeStamp)
Determine whether the given time (in milliseconds) is 'included' by the Calendar.