关于java:Quartz-Misfire

14次阅读

共计 4316 个字符,预计需要花费 11 分钟才能阅读完成。

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

正文完
 0