Quartz Scheduler misfireThreshold屬性的意義與觸發器超時後的處理策略

Quartz misfireThreshold屬性的意義與觸發器超時後的處理策略。html

 

在配置quartz.properties有這麼一個屬性就是misfireThreshold,用來指定調度引擎設置觸發器超時的"臨界值"。java

要弄清楚觸發器超時臨界值的意義,那麼就要先弄清楚什麼是觸發器超時?打個比方:好比調度引擎中有5個線程,而後在某天的下午2點 有6個任務須要執行,那麼因爲調度引擎中只有5個線程,因此在2點的時候會有5個任務會按照以前設定的時間正常執行,有1個任務由於沒有線程資源而被延遲執行,這個就叫觸發器超時。api

 

那麼超時的時間又是如何計算的呢?還接着上面的例子說,好比一個(任務A)應該在2點的時候執行,可是在2點的時候調度引擎中的可用線程都在忙碌狀態中,或者調度引擎掛了,這都有可能發生,而後再2點05秒的時候恢復(有可用線程或者服務器從新啓動),也就是說(任務A)應該在2點執行 但如今晚了5秒鐘。那麼這5秒鐘就是任務超時時間,或者叫作觸發器(Trigger)超時時間。服務器

 

理解了上面的內容再來看misfireThreshold值的意義,misfireThreshold是用來設置調度引擎對觸發器超時的忍耐時間,簡單來講 假設misfireThreshold=6000(單位毫秒)。ide

那麼它的意思說當一個觸發器超時時間若是大於misfireThreshold的值 就認爲這個觸發器真正的超時(也叫Misfires)。測試

若是一個觸發器超時時間 小於misfireThreshold的值, 那麼調度引擎則不認爲觸發器超時。也就是說調度引擎能夠忍受這個超時的時間。ui

 

仍是 任務A 比它應該正常的執行時間晚了5秒 那麼misfireThreshold的值是6秒,那麼調度引擎認爲這個延遲時間能夠忍受,因此不算超時(Misfires),那麼引擎會按照正常狀況執行該任務。 可是若是 任務A  比它應該正常的執行時間晚了7秒 或者是6.5秒 只要大於misfireThreshold  那麼調度引擎就會認爲這個任務的觸發器超時。this

 

這樣的話就會出現這麼狀況,讓咱們一個一個說明,並給出例子。lua

第一種狀況:任務一切正常,即按照咱們定義的觸發器的預期時間執行,好比下午2點運行 時間間隔3秒 重複運行 5次等,這個沒啥好說的。spa

 

第二種狀況:任務出現延時,延時的時間<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,那麼調度引擎該如何處理這個任務呢?

答案是這樣的:在定義一個任務的觸發器的時候,咱們能夠設置它超時的處理策略,調度引擎會根據咱們設置的策略來處理這個任務。

咱們在定義一個任務的觸發器時 最經常使用的就是倆種觸發器:一、SimpleTrigger 二、CronTrigger。

 

一、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.

 

大意是:

一、若是觸發器的重複執行數(Repeat Count)等於0,那麼會按這個(MISFIRE_INSTRUCTION_FIRE_NOW)策略執行。

二、若是觸發器的重複執行次數是 SimpleTrigger.REPEAT_INDEFINITELY (常量值爲-1,意思是重複無限次) ,那麼會按照MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT策略執行。

三、若是觸發器的重複執行次數大於0,那麼按照 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT執行。

 

既然是這樣,那就讓咱們依次看一下每種處理策略都是啥意思!

一、MISFIRE_INSTRUCTION_FIRE_NOW

Instructs the Scheduler that upon a mis-fire situation, the SimpleTrigger wants to be fired now by 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 相同

 

二、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的是獲取這個觸發器已經被觸發了多少次,那麼用原來的次數 減掉 已經觸發的次數就是還要觸發多少次

接下來就是判斷一下觸發器是否到告終束時間,若是到了的話,觸發器就不會在被觸發。

而後就是從新設置觸發器的開始實現是 「如今」 而且當即運行。

 

三、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。這回知道啥意思了吧。

 

四、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);
            } 
        }

  

五、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秒執行。

 

六、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次, 追上前面落下的執行次數。

 


 

 

二、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

 

一、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());
        }
    }

 

二、MISFIRE_INSTRUCTION_DO_NOTHING

這個策略與策略(1)正好相反,它不會被當即觸發,而是獲取下一個被觸發的時間,而且若是下一個被觸發的時間超出了end-time 那麼觸發器就不會被執行。

上面綠色標記的地方是源碼

補充幾個方法的說明:

一、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.

 

二、isTimeIncluded 判斷給定的時間是否包含在quartz的日曆當中,由於quartz是能夠自定義日曆的,設置哪些日子是節假日什麼的。

 

boolean isTimeIncluded(long timeStamp)
Determine whether the given time (in milliseconds) is 'included' by the Calendar.
相關文章
相關標籤/搜索