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
下一篇 easypoi 模板导出时的公式及foreach合并单元格问题


45 声望17 粉丝