Misfire,看到很多文章翻译为“失火”,其实感觉这个翻译和他本身代表的含义差距比较大。
Quartz中的fire表示触发器被触发的意思,Misfire则表示本来应该到了触发器被触发的时间,但是实际却没有触发,是“错过触发”的意思。
Misfire的原因
触发器Misfire的原因大概有以下几种:
- 触发器在初始化时设置的start时间小于当前系统时间
- jobStore设置为JDBC(通过数据库持久化存储),系统down机后再次启动
- 任务调度线程阻塞
- 没有可用任务执行线程(被其他正在执行中的任务占满)
Misfire的处理策略
发生Misfire之后,Quartz的不同Trigger会提供不同的处理策略。其中SimplerTrigger和CronTrigger的共同处理策略是:
- MISFIRE_INSTRUCTION_SMART_POLICY:smart policy,其实最终实现取决于Trigger实现类
- MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY:忽略Misfire就当Misfire没有发生,意思是任务调度器错过了某一触发器的触发时间之后,一旦该触发器获得执行机会后,会把所有错过触发的时间补回来。比如simpleTrigger设置为每15秒执行一次,由于某种原因在最近5分钟内错过了触发,则一旦其获得执行机会后,会连续触发5*(60/15)=20次
SimplerTrigger的处理策略包括:
- MISFIRE_INSTRUCTION_FIRE_NOW:对于一次性任务则立即触发,对于设置了执行次数repeat count的循环执行的任务,则等同于MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT。
- MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT:立即触发(不考虑Calendar的限制),而且触发次数不受影响(即使Misfre也能保证触发次数),比如设置执行100次,已执行10次,Misfire了5次,则剩余执行次数为100-10=90次(我们称之为触发次数不受影响)。但是如果同时设置了EndTime,则需要遵守EndTime的限制(如果当前时间已经超过的EndTime则不再触发)。
- MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT:立即触发(不考虑Calendar的限制),但是触发次数会受到影响:将Misfire的次数也考虑在内,比如设置为执行100次,已执行10次,Misfire了5次,则剩余次数为100-(10+5)=85次,所以错过的5次实际上还是被错过了。而且需要遵守EndTime的约定,如果当前时间已经超过的EndTime则不再触发。
- MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT:立即触发(考虑Calendar的限制),触发次数会受到影响,需要遵守EndTime的限制。
- MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT:立即触发(考虑Calendar的限制),需遵守EndTime的限制,触发次数不受影响。
- MISFIRE_INSTRUCTION_SMART_POLICY:默认处理策略,如果是不重复(只触发一次)触发器,等同于MISFIRE_INSTRUCTION_FIRE_NOW,如果是触发无数次的(永不停止)触发器,等同于MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT,否则如果是触发有限次数的触发器,等同于MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT。
CronTrigger的处理策略:
- MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:立即触发。
- MISFIRE_INSTRUCTION_DO_NOTHING:忽略Misfire就相当于Misfire没有发生过一样,具体来说就是用当前日期再次计算下次触发时间(考虑Calendar的限制),当前时间并不触发。
- 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合并单元格问题