要弄清楚做业的misfire,首先须要了解几个重要的概念:html
** 触发器超时 **:java
举个例子说明这个概念。好比调度引擎中有5个线程,而后在某天的下午2点 有6个任务须要执行,那么因为调度引擎中只有5个线程,因此在2点的时候会有5个任务会按照以前设定的时间正常执行,有1个任务由于没有线程资源而被延迟执行,这个就叫触发器超时。下面这些状况会形成触发器超时:git
1)系统由于某些缘由被重启。在系统关闭到从新启动之间的一段时间里,可能有些任务会 被 misfire;github
2)Trigger 被暂停(suspend)的一段时间里,有些任务可能会被 misfire;ide
3)线程池中全部线程都被占用,致使任务没法被触发执行,形成 misfire;ui
4)有状态任务在下次触发时间到达时,上次执行尚未结束;spa
** misfireThreshold **:.net
misfireThreshold 即触发器超时的临界值,它能够在quartz.properties文件中配置。misfireThreshold是用来设置调度引擎对触发器超时的忍耐时间。假设misfireThreshold设置为6000(单位毫秒),那么它的意思说当一个触发器超时时间大于misfireThreshold时,调度器引擎就认为这个触发器真正的超时(即Misfires)。换言之,若是一个触发器超时时间小于设定的misfireThreshold, 那么调度引擎则不认为触发器超时。也就是说这个job并没发生misfire。线程
quartz.properties中的配置code
#断定job为misfire的阈值,这里设置为4S org.quartz.jobStore.misfireThreshold = 4000
那么,调度器对于触发器超时可是超时时间小于misfireThreshold 或者 触发器已经misfire 的两种状况是怎么处理的呢?
为了制造超时的现象,实验时把线程池的大小设定为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,而后按照前面制定的周期性任务正常运行。
对于触发器超时,而且超时时间大于设定的misfireThreshold 这种状况,调度器引擎为简单触发器SimpleTrigger和表达式CronTrigger提供了多种处理策略,咱们能够在定义触发器时指定须要的策略。
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次, 追上前面落下的执行次数。
MISFIRE_INSTRUCTION_FIRE_ONCE_NOW: 指示触发器超时后会被当即安排执行。
MISFIRE_INSTRUCTION_DO_NOTHING: 这个策略与策略 MISFIRE_INSTRUCTION_FIRE_ONCE_NOW 正好相反,它不会被当即触发,而是获取下一个被触发的时间,而且若是下一个被触发的时间超出了end-time 那么触发器就不会被执行。
代码:https://git.oschina.net/thinwonton/QuartzDemo
参考博文:http://www.faceye.net/search/110706.html
misfire顾名思义, 就是quartz在应该触发trigger的时候未能及时将其触发( 缘由多是线程池没有线程可用 ), 这将致使trigger的下次触发时间落在在当前时间以前, 那么按照正常的quartz调度流程, 该trigger就再没有机会被调度了. 因为一个调度器实例在每次调度过程当中都会有必定的睡眠时间, 因此存在一段时间内全部调度器实例都在睡眠, 这也会使trigger不能被及时触发. 所以调度器须要每隔一段时间( 15s ~ 60s )查看一次各个trigger的nextfiretime( 即下次触发的时间 ), 检查出是否有trigger的下次触发时间落在当前时间以前足够长的时间, 在这里系统设定了一个60s的域( misfireThreshold ), 当一个trigger下一次触发时间早于当前时间60s以外, 调度器断定该触发器misfire. 为了在发现触发器misfire以后启动相应的流程恢复trigger至正常的状态, quartz在trigger中可设置相应的策略.
withMisfireHandlingInstructionDoNothing
不触发当即执行
等待下次Cron触发频率到达时刻开始按照Cron频率依次执行
以错过的第一个频率时间马上开始执行
重作错过的全部频率周期后
当下一次触发频率发生时间大于当前时间后,再按照正常的Cron频率依次执行
以当前时间为触发频率马上触发一次执行
而后按照Cron频率依次执行
withMisfireHandlingInstructionFireNow
以当前时间为触发频率当即触发执行
执行至FinalTIme的剩余周期次数
以调度或恢复调度的时刻为基准的周期频率,FinalTime根据剩余次数和当前时间计算获得
调整后的FinalTime会略大于根据starttime计算的到的FinalTime值
withMisfireHandlingInstructionIgnoreMisfires
以错过的第一个频率时间马上开始执行
重作错过的全部频率周期
当下一次触发频率发生时间大于当前时间之后,按照Interval的依次执行剩下的频率
共执行RepeatCount+1次
withMisfireHandlingInstructionNextWithExistingCount
不触发当即执行
等待下次触发频率周期时刻,执行至FinalTime的剩余周期次数
以startTime为基准计算周期频率,并获得FinalTime
即便中间出现pause,resume之后保持FinalTime时间不变
withMisfireHandlingInstructionNowWithExistingCount
以当前时间为触发频率当即触发执行
执行至FinalTIme的剩余周期次数
以调度或恢复调度的时刻为基准的周期频率,FinalTime根据剩余次数和当前时间计算获得
调整后的FinalTime会略大于根据starttime计算的到的FinalTime值
withMisfireHandlingInstructionNextWithRemainingCount
不触发当即执行
等待下次触发频率周期时刻,执行至FinalTime的剩余周期次数
以startTime为基准计算周期频率,并获得FinalTime
即便中间出现pause,resume之后保持FinalTime时间不变
withMisfireHandlingInstructionNowWithRemainingCount
以当前时间为触发频率当即触发执行
执行至FinalTIme的剩余周期次数
以调度或恢复调度的时刻为基准的周期频率,FinalTime根据剩余次数和当前时间计算获得
调整后的FinalTime会略大于根据starttime计算的到的FinalTime值
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
此指令致使trigger忘记原始设置的starttime和repeat-count 触发器的repeat-count将被设置为剩余的次数 这样会致使后面没法得到原始设定的starttime和repeat-count值