閱讀297 返回首頁    go 阿裏雲 go 技術社區[雲棲]


quartz - misfire錯過觸發時機的處理

1. 引言

要弄清楚作業的misfire,首先需要了解幾個重要的概念:

觸發器超時 :

舉個例子說明這個概念。比如調度引擎中有5個線程,然後在某天的下午2點 有6個任務需要執行,那麼由於調度引擎中隻有5個線程,所以在2點的時候會有5個任務會按照之前設定的時間正常執行,有1個任務因為沒有線程資源而被延遲執行,這個就叫觸發器超時。下麵這些情況會造成觸發器超時:

1)係統因為某些原因被重啟。在係統關閉到重新啟動之間的一段時間裏,可能有些任務會 被 misfire;

2)Trigger 被暫停(suspend)的一段時間裏,有些任務可能會被 misfire;

3)線程池中所有線程都被占用,導致任務無法被觸發執行,造成 misfire;

4)有狀態任務在下次觸發時間到達時,上次執行還沒有結束;

misfireThreshold :

misfireThreshold 即觸發器超時的臨界值,它可以在quartz.properties文件中配置。misfireThreshold是用來設置調度引擎對觸發器超時的忍耐時間。假設misfireThreshold設置為6000(單位毫秒),那麼它的意思說當一個觸發器超時時間大於misfireThreshold時,調度器引擎就認為這個觸發器真正的超時(即Misfires)。換言之,如果一個觸發器超時時間小於設定的misfireThreshold, 那麼調度引擎則不認為觸發器超時。也就是說這個job並沒發生misfire。

quartz.properties中的配置

#判定job為misfire的閾值,這裏設置為4S
org.quartz.jobStore.misfireThreshold = 4000

那麼,調度器對於觸發器超時但是超時時間小於misfireThreshold 或者 觸發器已經misfire 的兩種情況是怎麼處理的呢?

2. 調度器怎麼處理超時

2.1 timeout < misfireThreshold

為了製造超時的現象,實驗時把線程池的大小設定為1,misfireThreshold設定為5S。實驗中定義了兩個job,一個是busy job,它在運行期休眠了3S(<misfireThreshold ),另一個是TimeoutJob。我們為TimeoutJob定義了一個timeoutTrigger觸發器,觸發器每隔1S會運行一次TimeoutJob,總共運行7次。通過這樣的設定,在busy job占用了線程後,timeout job的觸發器已經超時,在3秒的運行期中timeout job觸發器錯過了3次作業運行時機。OK,下麵運行代碼看看調度器怎麼處理這個問題。

//BusyJob.java
public class BusyJob implements Job {
    private final Logger logger = LoggerFactory.getLogger(BusyJob.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String jobName = context.getJobDetail().getKey().getName();

        logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 開始執行");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 執行完畢");
    }

}
//TimeoutJob.java
public class TimeoutJob implements Job {
    private final Logger logger = LoggerFactory.getLogger(TimeoutJob.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        String jobName = context.getJobDetail().getKey().getName();

        logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 開始執行");

        logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 執行完畢");
    }

}
//TimeoutButNotMisfireTest.java
/**
 * 觸發器超時,但沒有misfire
 */
public class TimeoutButNotMisfireTest {

    public static void main(String[] args) throws SchedulerException, InterruptedException {

        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        // busy job
        JobDetail busyJob = JobBuilder //
                .newJob(BusyJob.class)//
                .withIdentity("busy job", "group1")//
                .build();

        SimpleTrigger busyTrigger = TriggerBuilder //
                .newTrigger() //
                .withIdentity("busy job trigger", "group1")//
                .startNow() //
                .withPriority(5) // 高優先級
                .withSchedule(SimpleScheduleBuilder.simpleSchedule() //
                        .withRepeatCount(0) //
                ).build();
        scheduler.scheduleJob(busyJob, busyTrigger);

        // timeout job
        JobDetail timeoutJob = JobBuilder //
                .newJob(TimeoutJob.class)//
                .withIdentity("timeout job", "group2")//
                .build();

        SimpleTrigger timeoutTrigger = TriggerBuilder //
                .newTrigger() //
                .withIdentity("timeout job trigger", "group2")//
                .startNow() //立即觸發
                .withPriority(1) // 低優先級
                .withSchedule(SimpleScheduleBuilder.simpleSchedule() //
                        .withIntervalInSeconds(1) //每隔1S觸發一次
                        .withRepeatCount(7) // 循環7次
                ).build();
        scheduler.scheduleJob(timeoutJob, timeoutTrigger);

        scheduler.start();

        Thread.sleep(20 * 1000);

        scheduler.shutdown(true);

    }
}

運行結果:

INFO  11:31:11,420 com.github.thinwonton.quartz.sample.misfire.BusyJob: [busy job] 在  : [ 11:31:11] 開始執行
 INFO  11:31:14,420 com.github.thinwonton.quartz.sample.misfire.BusyJob: [busy job] 在  : [ 11:31:14] 執行完畢
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 開始執行
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 執行完畢
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 開始執行
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 執行完畢
 INFO  11:31:14,423 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 開始執行
 INFO  11:31:14,423 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 執行完畢
 INFO  11:31:14,426 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 開始執行
 INFO  11:31:14,426 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 執行完畢
 INFO  11:31:15,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:15] 開始執行
 INFO  11:31:15,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:15] 執行完畢
 INFO  11:31:16,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:16] 開始執行
 INFO  11:31:16,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:16] 執行完畢
 INFO  11:31:17,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:17] 開始執行
 INFO  11:31:17,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:17] 執行完畢
 INFO  11:31:18,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:18] 開始執行
 INFO  11:31:18,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:18] 執行完畢

通過觀察運行結果,我們可以得到結論:

超時的觸發器(超時時間小於misfireThreshold)在獲取到運行線程後,將會立即運行前麵錯過的作業job,然後按照前麵製定的周期性任務正常運行。

2.2 timeout >= misfireThreshold

對於觸發器超時,並且超時時間大於設定的misfireThreshold 這種情況,調度器引擎為簡單觸發器SimpleTrigger和表達式CronTrigger提供了多種處理策略,我們可以在定義觸發器時指定需要的策略。

2.2.1 對於SimpleTrigger的處理策略

  • MISFIRE_INSTRUCTION_FIRE_NOW : 調度引擎在MisFire的情況下,將任務(JOB)馬上執行一次。需要注意的是 這個指令通常被用做隻執行一次的Triggers,也就是沒有重複的情況(non-repeating),如果這個Triggers的被安排的執行次數大於0。那麼這個執行與 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT 相同。
  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT: 調度引擎重新調度該任務,repeat count 保持不變,按照原有製定的執行方案執行repeat count次,但是,如果當前時間,已經晚於 end-time,那麼這個觸發器將不會再被觸發。舉個例子:比如一個觸發器設置的時間是 10:00 執行時間間隔10秒 重複10次。那麼當10:07秒的時候調度引擎可以執行這個觸發器的任務,然後按照原有製定的時間間隔執行10次。但是如果觸發器設置的執行時間是10:00,結束時間為10:10,由於種種原因導致該觸發器在10:11分才能被調度引擎觸發,這時,觸發器將不會被觸發了。
  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT: 這個策略跟上麵的 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 策略類似,唯一的區別就是調度器觸發觸發器的時間不是“現在” 而是下一個 scheduled time。
  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT: 這個策略跟上麵的策略 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 比較類似,調度引擎重新調度該任務,repeat count 是剩餘應該執行的次數,也就是說本來這個任務應該執行10次,但是已經錯過了3次,那麼這個任務就還會執行7次。
  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT: 這個策略跟上麵的 MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT 策略類似,區別就是repeat count 是剩餘應該執行的次數而不是全部的執行次數。比如一個任務應該在2:00執行,repeat count=5,時間間隔5秒, 但是在2:07才獲得執行的機會,那任務不會立即執行,而是按照機會在2點10秒執行。
  • MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY: 這個策略跟上麵的 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 策略類似,但這個策略是忽略所有的超時狀態,快速執行之前錯過的次數,然後再按照之前製定的周期觸發觸發器。舉個例子,一個SimpleTrigger 每個15秒鍾觸發, 但是超時了5分鍾才獲得執行的機會,那麼這個觸發器會被快速連續調用20次, 追上前麵落下的執行次數。

2.2.2 對於CronTrigger的處理策略

  • MISFIRE_INSTRUCTION_FIRE_ONCE_NOW: 指示觸發器超時後會被立即安排執行。
  • MISFIRE_INSTRUCTION_DO_NOTHING: 這個策略與策略 MISFIRE_INSTRUCTION_FIRE_ONCE_NOW 正好相反,它不會被立即觸發,而是獲取下一個被觸發的時間,並且如果下一個被觸發的時間超出了end-time 那麼觸發器就不會被執行。

原文鏈接

最後更新:2017-06-30 15:02:13

  上一篇:go  Bootstrap Table筆記——1
  下一篇:go  redis持久化(persistence)