Misfire,看到很多文章翻译为“失火”,其实感觉这个翻译和他自身代表的含意差距比拟大。

Quartz中的fire示意触发器被触发的意思,Misfire则示意原本应该到了触发器被触发的工夫,然而理论却没有触发,是“错过触发”的意思。

Misfire的起因

触发器Misfire的起因大略有以下几种:

  1. 触发器在初始化时设置的start工夫小于以后零碎工夫
  2. jobStore设置为JDBC(通过数据库长久化存储),零碎down机后再次启动
  3. 任务调度线程阻塞
  4. 没有可用工作执行线程(被其余正在执行中的工作占满)

Misfire的解决策略

产生Misfire之后,Quartz的不同Trigger会提供不同的解决策略。其中SimplerTrigger和CronTrigger的独特解决策略是:

  1. MISFIRE_INSTRUCTION_SMART_POLICY:smart policy,其实最终实现取决于Trigger实现类
  2. MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY:疏忽Misfire就当Misfire没有产生,意思是任务调度器错过了某一触发器的触发工夫之后,一旦该触发器取得执行机会后,会把所有错过触发的工夫补回来。比方simpleTrigger设置为每15秒执行一次,因为某种原因在最近5分钟内错过了触发,则一旦其取得执行机会后,会间断触发5*(60/15)=20次

SimplerTrigger的解决策略包含:

  1. MISFIRE_INSTRUCTION_FIRE_NOW:对于一次性工作则立刻触发,对于设置了执行次数repeat count的循环执行的工作,则等同于MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT。
  2. MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT:立刻触发(不思考Calendar的限度),而且触发次数不受影响(即便Misfre也能保障触发次数),比方设置执行100次,已执行10次,Misfire了5次,则残余执行次数为100-10=90次(咱们称之为触发次数不受影响)。然而如果同时设置了EndTime,则须要恪守EndTime的限度(如果以后工夫曾经超过的EndTime则不再触发)。
  3. MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT:立刻触发(不思考Calendar的限度),然而触发次数会受到影响:将Misfire的次数也思考在内,比方设置为执行100次,已执行10次,Misfire了5次,则剩余次数为100-(10+5)=85次,所以错过的5次实际上还是被错过了。而且须要恪守EndTime的约定,如果以后工夫曾经超过的EndTime则不再触发。
  4. MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT:立刻触发(思考Calendar的限度),触发次数会受到影响,须要恪守EndTime的限度。
  5. MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT:立刻触发(思考Calendar的限度),需恪守EndTime的限度,触发次数不受影响。
  6. MISFIRE_INSTRUCTION_SMART_POLICY:默认解决策略,如果是不反复(只触发一次)触发器,等同于MISFIRE_INSTRUCTION_FIRE_NOW,如果是触发无数次的(永不进行)触发器,等同于MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT,否则如果是触发无限次数的触发器,等同于MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT。

CronTrigger的解决策略:

  1. MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:立刻触发。
  2. MISFIRE_INSTRUCTION_DO_NOTHING:疏忽Misfire就相当于Misfire没有产生过一样,具体来说就是用以后日期再次计算下次触发工夫(思考Calendar的限度),以后工夫并不触发。
  3. MISFIRE_INSTRUCTION_SMART_POLICY:默认的解决策略,等同于MISFIRE_INSTRUCTION_FIRE_ONCE_NOW。

Misfire解决策略总结

Misfire解决策略看起来非常复杂,尤其是SimpleTrigger的策略,然而个别状况下咱们并不需要这么简单的Misfire策略,所以绝大部分状况咱们只须要应用默认的策略就能够。

非凡状况下,比方严格要求到EndTime必须执行够多少次这类需要,就须要咱们认真钻研不同策略的区别,采纳适合的Misfire策略能力确保满足需要。

不论是SimplerTrigger还是CronTrigger,默认的Misfire解决策略都是MISFIRE_INSTRUCTION_SMART_POLICY,所谓smart policy其实就是能够依据Trigger的不同个性做出不同的解决。

Misfire的设置

触发器创立的时候通过TriggerBuilder的ScheduleBuilder指定,SimpleSchduleBuilder对应设置SimpleTrigger的Misfire解决策略,CronScheduleBuilder对应设置CronTrigger的Misfire策略。

Trigger trigger = newTrigger()                .withIdentity("myTriggger","MyGroup")                .startNow()                .withSchedule(simpleSchedule()                        .withIntervalInSeconds(20)                        .withMisfireHandlingInstructionFireNow())                .build();

Misfire解决策略源码剖析

任务调度线程QuartzSchedulerThread的run办法咱们后面曾经简略做过剖析,Quartz的任务调度就是在这里进行的。其中会调用JobStore的acquireNextTriggers办法获取在idleWaitTime工夫范畴内须要被调度的Trigger。

以RAMJobStore为例,acquireNextTriggers办法会逐个获取timeTriggers中的Trigger,之后首先调用applyMisfire(tw)进行Misfire解决。

applyMisfire(tw)办法首先判断Trigger的Misfire策略如果是IGNORE_MISFIRE_POLICY,或者Trigger的触发工夫大于零碎工夫减去零碎设置的Misfire容忍工夫,则不认为是Misfire,返回false。否则会认为是Misfire,进行Misfire时候的后续解决。

  long misfireTime = System.currentTimeMillis();        if (getMisfireThreshold() > 0) {            misfireTime -= getMisfireThreshold();        } Date tnft = tw.trigger.getNextFireTime();        if (tnft == null || tnft.getTime() > misfireTime                 || tw.trigger.getMisfireInstruction() == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) {             return false;         }

这里须要留神的是第二个条件,“Trigger的触发工夫大于零碎工夫减去零碎设置的Misfire容忍工夫”,咱们须要举例简略剖析一下。

假如咱们设置的Misfire容忍工夫是60秒,Trigger的触发工夫为7点55分整。

如果以后零碎工夫是7点56,减去容忍工夫60秒之后(咱们临时称之为Misfire工夫)是7点55,Trigger的触发工夫7点55分并不大于Misfire工夫,零碎认为产生了Misfire。而且以后工夫如果大于7点56分的话,都会认为是产生了Misfire。

如果以后工夫是7点55分59秒,减去容忍工夫60秒之后的Misfire工夫是7点54分59秒,*Trigger的触发工夫7点55分大于Misfire工夫,所以零碎认为没有产生Misfire。

如果产生了Misfire,调用trigger的updateAfterMisfire办法进行产生Misfire后的解决,Misfire解决策略就是在这个updateAfterMisfire办法中失效的,updateAfterMisfire办法的解决逻辑体现了不同Trigger的Misfire解决策略。

updateAfterMisfire的解决逻辑其实就是Trigger的不同Misfire解决策略的实现过程,钻研updateAfterMisfire办法源码是了解Misfire解决策略的最好的方法。

如果applyMisfire(tw)办法返回true,也就是说Quartz认为产生了Misfire,则零碎判断通过Misfire解决之后的Trigger的下次触发工夫不为空的话,就会把以后trigger从新加回到timeTriggers中,继续执行下次循环。

      if (applyMisfire(tw)) {                    if (tw.trigger.getNextFireTime() != null) {                        timeTriggers.add(tw);                    }                    continue;                }

其实咱们剖析Trigger的不同Misfire策略后会发现,只有产生了Misfire、而且在Misfire解决之后Trigger的下次执行工夫不为空,则不论是什么Misfire策略均示意要在以后工夫触发该Trigger。

既然要在以后工夫触发该Trigger,为什么还要把Trigger放回timeTriggers、而不是间接执行该Trigger?集体认为起因是,放回去从新排序,因为timeTrigger是一个有序队列,其中很可能会有触发工夫比以后工夫更早的其余触发器,应该在触发以后这个Misfired的Trigger之前被触发。

Over!

上一篇 Quartz - Trigger & RAMJobStore