Quartz的misfire处理机制分析
Quartz是一个特性丰富的开源的任务调度开发库,它可以很方便的集成到你的应用程序中。在Quartz中,当一个持久的触发器因为调度器被关闭或者线程池中没有可用的线程而错过了激活时间时,就会发生激活失败(misfire)。那么,我们需要明确2个问题:如何判定激活失败;如何处理激活失败。
一、激活失败判定
quartz.properties配置文件中有一个属性是misfireThreshold(单位为毫秒),用来指定调度引擎设置触发器超时的"临界值"。也就是说Quartz对于任务的超时是有容忍度的,超过了这个容忍度才会判定misfire。比如说,某触发器设置为,10:15首次激活,然后每隔3秒激活一次,无限次重复。然而该任务每次运行需要10秒钟的时间。可见,每次任务的执行都会超时,那么究竟是否会引起misfire,就取决于misfireThreshold的值了。以第二次任务来说,它的运行时间已经比预定晚了7秒,那么如果misfireThreshold>7000,说明该偏差可容忍,则不算misfire,该任务立刻执行;如果misfireThreshold<=7000,则判定为misfire,根据相关配置策略进行处理。
注意,任务的延迟是有累计的。在前面的例子中,假设misfireThreshold设置为60000,即60秒。那么每次任务的延迟量即是否misfire计算如下:
任务编号 |
预定运行时刻 |
实际运行时刻 |
延迟量(秒) |
备注 |
1 |
10:15 |
10:15 |
0 |
|
2 |
10:18 |
10:25 |
7 |
|
3 |
10:21 |
10:35 |
14 |
|
4 |
10:24 |
10:45 |
21 |
|
5 |
10:27 |
10:55 |
28 |
|
6 |
10:30 |
11:05 |
35 |
|
7 |
10:33 |
11:15 |
42 |
|
8 |
10:36 |
11:25 |
49 |
|
9 |
10:39 |
11:35 |
56 |
|
10 |
10:42 |
11:45 |
63 |
misfire |
从表中可以看到,每一次任务执行都会带来7秒的延迟量,该延迟量不断被与misfireThreshold比较,直到达到该值后,在11:45分发生misfire。那么在11:45第10次任务会不会准时执行呢?答案是不一定,取决于配置。
二、激活失败处理
激活失败指令(Misfire Instructions)是触发器的一个重要属性,它指定了misfire发生时调度器应当如何处理。所有类型的触发器都有一个默认的指令,叫做Trigger.MISFIRE_INSTRUCTION_SMART_POLICY,但是这个这个“聪明策略”对于不同类型的触发器其具体行为是不同的。对于SimpleTrigger,这个“聪明策略”将根据触发器实例的状态和配置来决定其行为。具体如下[]:
如果Repeat Count=0:
instruction selected = MISFIRE_INSTRUCTION_FIRE_NOW;
如果Repeat Count=REPEAT_INDEFINITELY:
instruction selected = MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT;
如果Repeat Count>0:
instruction selected = MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT;
下面解释SimpleTrigger常见策略:
MISFIRE_INSTRUCTION_FIRE_NOW
立刻执行。对于不会重复执行的任务,这是默认的处理策略。
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
NEXT指以现在为基准,以repeat interval为周期,延时到下一个激活点执行。WITH_REMAINING_COUNT指超时期内错过的执行机会作废。因此该策略的含义是,在下一个激活点执行,且超时期内错过的执行机会作废。
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_COUNT
立即执行,且超时期内错过的执行机会作废。
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT
WITH_EXISTING_COUNT指,根据已设置的repeat count进行执行。也就是说错过的执行机会不作废,保证实际执行次数满足设置。因此本策略的含义是,在下一个激活点执行,并重复到指定的次数。
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_COUNT
立即执行,并重复到指定的次数。
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
忽略所有的超时状态,按照触发器的策略执行。
对于CronTrigger,该“聪明策略”默认选择MISFIRE_INSTRUCTION_FIRE_ONCE_NOW以指导其行为。
下面解释CronTrigger常见策略:
MISFIRE_INSTRUCTION_FIRE_ONCE_NOW
立刻执行一次,然后就按照正常的计划执行。
MISFIRE_INSTRUCTION_DO_NOTHING
目前不执行,然后就按照正常的计划执行。这意味着如果下次执行时间超过了end time,实际上就没有执行机会了。
三、示例程序1
=====代码=====
JobDetail job1 = newJob(StatefulDumbJob.class).withIdentity("statefulJob1", "group1") .usingJobData(StatefulDumbJob.EXECUTION_DELAY, 10000L).build(); SimpleTrigger trigger1 = newTrigger().withIdentity("trigger1", "group1").startAt(startTime) .withSchedule(simpleSchedule().withIntervalInSeconds(3).repeatForever()).build(); Date ft1 = sched.scheduleJob(job1, trigger1); log.info(job1.getKey() + " will run at: " + ft1 + " and repeat: " + trigger1.getRepeatCount() + " times, every " + trigger1.getRepeatInterval() / 1000 + " seconds");
=====代码=====
misfireThreshold设置为60000。
分析:
各任务的延迟量如下表所示:
任务编号 |
预定运行时刻 |
实际运行时刻 |
延迟量(秒) |
备注 |
1 |
16:15:45 |
16:15:45 |
0 |
|
2 |
16:15:48 |
16:15:55 |
7 |
|
3 |
16:15:51 |
16:16:05 |
14 |
|
4 |
16:15:54 |
16:16:15 |
21 |
|
5 |
16:15:57 |
16:16:25 |
28 |
|
6 |
16:16:00 |
16:16:35 |
35 |
|
7 |
16:16:03 |
16:16:45 |
42 |
|
8 |
16:16:06 |
16:16:55 |
49 |
|
9 |
16:16:09 |
16:17:05 |
56 |
|
无 |
16:16:12 |
16:17:15 |
63 |
misfire |
10 |
16:17:18 |
16:17:18 |
0 |
|
11 |
16:17:21 |
16:17:28 |
7 |
|
在16:17:15发生misfire。因为Repeat Count=REPEAT_INDEFINITELY,所以smart policy选择的策略是MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT。因此,延时3秒后,在16:17:18第10次任务被执行,延迟量清零。当然,程序会一直这样循环下去。
程序输出:
---group1.statefulJob1 executing.[Fri Feb 19 16:15:45 CST 2016]
-group1.statefulJob1 complete (1).
---group1.statefulJob1 executing.[Fri Feb 19 16:15:55 CST 2016]
-group1.statefulJob1 complete (2).
---group1.statefulJob1 executing.[Fri Feb 19 16:16:05 CST 2016]
-group1.statefulJob1 complete (3).
---group1.statefulJob1 executing.[Fri Feb 19 16:16:15 CST 2016]
-group1.statefulJob1 complete (4).
---group1.statefulJob1 executing.[Fri Feb 19 16:16:25 CST 2016]
-group1.statefulJob1 complete (5).
---group1.statefulJob1 executing.[Fri Feb 19 16:16:35 CST 2016]
-group1.statefulJob1 complete (6).
---group1.statefulJob1 executing.[Fri Feb 19 16:16:45 CST 2016]
-group1.statefulJob1 complete (7).
---group1.statefulJob1 executing.[Fri Feb 19 16:16:55 CST 2016]
-group1.statefulJob1 complete (8).
---group1.statefulJob1 executing.[Fri Feb 19 16:17:05 CST 2016]
-group1.statefulJob1 complete (9).
---group1.statefulJob1 executing.[Fri Feb 19 16:17:18 CST 2016]
-group1.statefulJob1 complete (10).
四、示例程序2
=====代码=====
JobDetail job2 = newJob(StatefulDumbJob.class).withIdentity("statefulJob2", "group1") .usingJobData(StatefulDumbJob.EXECUTION_DELAY, 10000L).build(); SimpleTrigger trigger2 = newTrigger() .withIdentity("trigger2", "group1") .startAt(startTime) .withSchedule(simpleSchedule().withIntervalInSeconds(3).repeatForever() .withMisfireHandlingInstructionNowWithExistingCount()) // set misfire instructions .build(); Date ft2 = sched.scheduleJob(job2, trigger2); log.info(job2.getKey() + " will run at: " + ft2 + " and repeat: " + trigger2.getRepeatCount() + " times, every " + trigger2.getRepeatInterval() / 1000 + " seconds");
=====代码=====
misfireThreshold设置为60000。
分析:
示例2与示例1的区别在于,触发器指定了激活失败指令为MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_COUNT。
各任务的延迟量如下表所示:
任务编号 |
预定运行时刻 |
实际运行时刻 |
延迟量(秒) |
备注 |
1 |
17:05:15 |
17:05:15 |
0 |
|
2 |
17:05:18 |
17:05:25 |
7 |
|
3 |
17:05:21 |
17:05:35 |
14 |
|
4 |
17:05:24 |
17:05:45 |
21 |
|
5 |
17:05:27 |
17:05:55 |
28 |
|
6 |
17:05:30 |
17:06:05 |
35 |
|
7 |
17:05:33 |
17:06:15 |
42 |
|
8 |
17:05:36 |
17:06:25 |
49 |
|
9 |
17:05:39 |
17:06:35 |
56 |
|
无 |
17:05:42 |
17:06:45 |
63 |
misfire |
10 |
17:06:45 |
17:06:45 |
0 |
|
11 |
17:05:48 |
17:06:55 |
7 |
|
在17:06:45发生misfire。根据其激活失败指令,任务10在该时刻被立即执行,延迟量清零。然后,程序继续如此循环下去。
程序输出:
---group1.statefulJob2 executing.[Fri Feb 19 17:05:15 CST 2016]
-group1.statefulJob2 complete (1).
---group1.statefulJob2 executing.[Fri Feb 19 17:05:25 CST 2016]
-group1.statefulJob2 complete (2).
---group1.statefulJob2 executing.[Fri Feb 19 17:05:35 CST 2016]
-group1.statefulJob2 complete (3).
---group1.statefulJob2 executing.[Fri Feb 19 17:05:45 CST 2016]
-group1.statefulJob2 complete (4).
---group1.statefulJob2 executing.[Fri Feb 19 17:05:55 CST 2016]
-group1.statefulJob2 complete (5).
---group1.statefulJob2 executing.[Fri Feb 19 17:06:05 CST 2016]
-group1.statefulJob2 complete (6).
---group1.statefulJob2 executing.[Fri Feb 19 17:06:15 CST 2016]
-group1.statefulJob2 complete (7).
---group1.statefulJob2 executing.[Fri Feb 19 17:06:25 CST 2016]
-group1.statefulJob2 complete (8).
---group1.statefulJob2 executing.[Fri Feb 19 17:06:35 CST 2016]
-group1.statefulJob2 complete (9).
---group1.statefulJob2 executing.[Fri Feb 19 17:06:45 CST 2016]
-group1.statefulJob2 complete (10).
---group1.statefulJob2 executing.[Fri Feb 19 17:06:55 CST 2016]
-group1.statefulJob2 complete (11).