前言
在使用Quartz 时,有时候我们的Trigger 会因为一些原因导致无法在预期的时间点被触发,从而导致实际触发时间点相较于预期时间点有延迟,当延迟的时间超过默认的1 分钟时,就会造成Trigger 的Misfire。
本文将针对Quartz 的Misfire 机制,进行机制说明与源码分析,希望帮助大家一文搞懂Quartz 的Misfire机制。
正文
一. Misfire机制说明
当发生如下情况让Trigger 错过正常触发时间时,就会触发Misfire机制。
- 应用所有实例全部重启或宕机。此时没有实例来执行定时任务,可能导致Trigger错过触发时间;
- 不允许并发执行的Trigger 上一次执行耗时超过了触发间隔。不能并发执行的Trigger如果上一次耗时超过了触发间隔,那么下一次触发时就会错过正常触发时间;
- 线程池没有可用线程。如果线程池长时间打满,会导致Trigger 无法正常被触发,此时可能会导致Trigger错过正常触发时间。
当Trigger 触发Misfire 机制时,根据Trigger的不同,有如下的策略进行选择。
1. SimpleTrigger的 Misfire机制
(对应的官方注释在 SimpleTrigger 接口中)
SimpleTrigger 的Misfire机制说明如下。
- MISFIRE_INSTRUCTION_FIRE_NOW(1)
如果Trigger 的REPEAT_COUNT == 0,则表现为在当前时刻立即触发一次。
如果Trigger 的REPEAT_COUNT > 0 ,则机制同MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT。
- MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT(2)
在当前时刻立即触发一次,并以当前时刻作为起始时间,按照触发间隔依次往后触发,总触发次数不变,也就是Trigger 的FINAL_FIRE_TIME会往后移。
- MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT(3)
在当前时刻立即触发一次,并以当前时刻作为起始时间,按照触发间隔依次往后触发,总触发次数减少,减少的次数等于在Misfire 期间错过的触发次数,也就是Trigger 的FINAL_FIRE_TIME 保持(大致)不变。
- MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT(4)
在下一个触发时间点正常触发,总触发次数减少,减少的次数等于在Misfire 期间错过的触发次数,也就是Trigger 的FINAL_FIRE_TIME 保持(大致)不变。
- MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT(5)
在下一个触发时间点正常触发,并按照触发间隔依次往后触发,总触发次数不变,也就是Trigger 的FINAL_FIRE_TIME会往后移。
2. CronTrigger的 Misfire机制
(对应的官方注释在 CronTrigger 接口中)
CronTrigger 的Misfire机制说明如下。
- MISFIRE_INSTRUCTION_FIRE_NOW(1)
立即触发一次,后续按照正常的Cron计划来触发。
- MISFIRE_INSTRUCTION_DO_NOTHING(2)
什么都不做,后续按照正常的Cron计划来触发。
3. 公共的 Misfire机制
(对应的官方注释在 Trigger 接口中)
公共的Misfire机制说明如下。
- MISFIRE_INSTRUCTION_SMART_POLICY(0)
是Quartz 的Misfire的默认触发机制。
对于SimpleTrigger 来说,会根据Trigger 的REPEAT_COUNT 的值来决定使用哪种Misfire机制。
如果REPEAT_COUNT == 0 ,则使用MISFIRE_INSTRUCTION_FIRE_NOW机制;
如果REPEAT_COUNT0 > 0 ,则使用MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT;
如果REPEAT_COUNT0 == REPEAT_INDEFINITELY ,也就是重复无限次 ,则使用MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT。
对于CronTrigger 来说,MISFIRE_INSTRUCTION_SMART_POLICY 会使用MISFIRE_INSTRUCTION_FIRE_NOW触发机制。
- MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY(-1)
立即将所有错过的触发给补上。
以SimpleTrigger 为例,假如我们有一个Trigger 的触发间隔是30s (REPEAT_INTERVAL=30000 ),然后Trigger 错过的时间达到了3 分钟,此时在MisfireHandlingInstructionIgnoreMisfires 机制下,会立刻一次性将错过的6次触发给补上,然后根据触发间隔来依次完成剩余的触发。
二. Misfire源码分析
在启动调度器的时候,会最终调用到MisfireHandler 的initialize() 方法,在这个方法中,就会将Misfire 的后台线程运行起来,这个Misfire 的后台线程,会不断的调用到JobStoreSupport 的doRecoverMisfires() 方法来完成Misfire ,下面从doRecoverMisfires() 方法开始分析。
java
protected RecoverMisfiredJobsResult doRecoverMisfires() throws JobPersistenceException {
boolean transOwner = false;
Connection conn = getNonManagedTXConnection();
try {
RecoverMisfiredJobsResult result = RecoverMisfiredJobsResult.NO_OP;
// 如果doubleCheckLockMisfireHandler配置为true
// 则在获取TRIGGER_ACCESS前先判断是否有Misfire的Trigger
// 以减少TRIGGER_ACCESS锁的获取
// 默认是true但如果总是存在Misfire的Trigger则要配成false
int misfireCount = (getDoubleCheckLockMisfireHandler()) ?
getDelegate().countMisfiredTriggersInState(
conn, STATE_WAITING, getMisfireTime()) :
Integer.MAX_VALUE;
if (misfireCount == 0) {
getLog().debug(
"Found 0 triggers that missed their scheduled fire-time.");
} else {
// 获取TRIGGER_ACCESS锁
transOwner = getLockHandler().obtainLock(conn, LOCK_TRIGGER_ACCESS);
// 实际的Misfire逻辑
result = recoverMisfiredJobs(conn, false);
}
// 提交事务
commitConnection(conn);
return result;
} catch (JobPersistenceException e) {
rollbackConnection(conn);
throw e;
} catch (SQLException e) {
rollbackConnection(conn);
throw new JobPersistenceException("Database error recovering from misfires.", e);
} catch (RuntimeException e) {
rollbackConnection(conn);
throw new JobPersistenceException("Unexpected runtime exception: "
+ e.getMessage(), e);
} finally {
try {
releaseLock(LOCK_TRIGGER_ACCESS, transOwner);
} finally {
cleanupConnection(conn);
}
}
}
处理Misfire 是需要加锁的,但是如果很少出现Misfire 的Trigger ,那么加锁开销太大,所以Quartz 在处理Misfire 前会先判断一下是否有需要Misfire 的Trigger,如果没有就不加锁了。
真正的Misfire 逻辑在recoverMisfiredJobs() 方法中,下面再跟进一下。
java
protected RecoverMisfiredJobsResult recoverMisfiredJobs(
Connection conn, boolean recovering)
throws JobPersistenceException, SQLException {
// 默认一个事务中处理的Misfire的Trigger数为20
int maxMisfiresToHandleAtATime =
(recovering) ? -1 : getMaxMisfiresToHandleAtATime();
List<TriggerKey> misfiredTriggers = new LinkedList<TriggerKey>();
long earliestNewTime = Long.MAX_VALUE;
// 判断为Misfire的需要满足如下条件
// 1. Trigger下一次触发时间小于当前时间减去misfireThreshold
// 2. qrtz_triggers表中Trigger状态是WAITING
// 其中misfireThreshold默认是1分钟
// 也就是Trigger延迟触发的时间在1分钟内都可以容忍
// 如果超过1分钟则判定为Misfire
boolean hasMoreMisfiredTriggers =
getDelegate().hasMisfiredTriggersInState(
conn, STATE_WAITING, getMisfireTime(),
maxMisfiresToHandleAtATime, misfiredTriggers);
if (hasMoreMisfiredTriggers) {
getLog().info(
"Handling the first " + misfiredTriggers.size() +
" triggers that missed their scheduled fire-time. " +
"More misfired triggers remain to be processed.");
} else if (misfiredTriggers.size() > 0) {
getLog().info(
"Handling " + misfiredTriggers.size() +
" trigger(s) that missed their scheduled fire-time.");
} else {
getLog().debug(
"Found 0 triggers that missed their scheduled fire-time.");
return RecoverMisfiredJobsResult.NO_OP;
}
for (TriggerKey triggerKey: misfiredTriggers) {
OperableTrigger trig =
retrieveTrigger(conn, triggerKey);
if (trig == null) {
continue;
}
// 在这里获取Misfire的机制并执行相应的逻辑
doUpdateOfMisfiredTrigger(conn, trig, false, STATE_WAITING, recovering);
if(trig.getNextFireTime() != null && trig.getNextFireTime().getTime() < earliestNewTime)
earliestNewTime = trig.getNextFireTime().getTime();
}
return new RecoverMisfiredJobsResult(
hasMoreMisfiredTriggers, misfiredTriggers.size(), earliestNewTime);
}
这里需要知道一个Trigger 如何被判定为Misfire,需要同时满足如下条件。
- Trigger 下一次触发时间(NEXT_FIRE_TIME )小于当前时间减去misfireThreshold 。misfireThreshold 默认是1 分钟,也就是Trigger 延迟触发的时间在1 分钟内都可以容忍,超过1 分钟才会被判定为Misfire;
- qrtz_triggers 表中Trigger 状态是WAITING。
判定为Misfire 的Trigger ,后续就会根据相应的机制执行相应的Misfire逻辑。
总结
一个Trigger 发生Misfire ,其实就是这个Trigger 因为一些原因错过了正常的触发时间点。Trigger 要判定为Misfire,要满足如下条件。
- Trigger 的状态是WAITING;
- Trigger 的下一次触发时间(NEXT_FIRE_TIME )小于当前时间减去misfireThreshold (默认 60s)。
什么情况会发生Misfire,通常有如下情况。
- 应用所有实例全部重启或宕机。此时没有实例来执行定时任务,可能导致Trigger错过触发时间;
- 不允许并发执行的Trigger 上一次执行耗时超过了触发间隔。不能并发执行的Trigger如果上一次耗时超过了触发间隔,那么下一次触发时就会错过正常触发时间;
- 线程池没有可用线程。如果线程池长时间打满,会导致Trigger 无法正常被触发,此时可能会导致Trigger错过正常触发时间。
如果发生了Misfire ,Quartz 针对不同类型的Trigger 提供了相应的弥补机制,具体机制说明可参见第一节的第1 ,2 和3小节。
总结不易,如果本文对你有帮助,烦请点赞,收藏加关注,谢谢帅气漂亮的你。