Quartz定时器中的misfire指定解析

原文作者:Posted 8th April 2012 by Tomasz Nurkiewicz 
有时候quartz并不像你希望的那样,能够在规定的时间执行定时任务。主要有以下三点原因:

  1. 全部的线程都执行其它任务(可能是具有高优先级的任务);
  2. scheduler(调度器)down掉了;
  3. 任务被调度从过去的某个时间开始执行(也许是编码错误)。

你可以增加执行任务的线程数,通过在quartz.properties文件中配置参数 
org.quartz.threadPool.threadCount,这个值默认为10。但是当application/server/scheduler这个三个元素Down掉的时候,你就无能为力了。

当quartz不能够依据触发条件触发执行任务的情况,叫做misfire。

当misfire发生的时候,quartz是怎么做的呢?事实证明,有各种策略,quartz叫指令(instrucations),并具有默认值,有些策略是你没有考虑到的,quartz也替你考虑了。但是为了程序的健壮性和执行时的可预期性(尤其是在高负载或维护期),你应该有意识的确保你的triggers和jobs的配置是按你的意图正确配置的。

根据不同的trigger,有不同的配置选项(misfire instruction)可以选择。根据quartz配置的触发策略不同,quartz展现的行为也不同(就是所谓的smart policy)

在进入细节之前,还有一个关于配置上的问题说明。那就是org.quartz.jobStore.misfireThreshold (in milliseconds),其默认值是60000毫秒(1分钟)。它定义了trigger被延迟多久才算是misfire。根据默认值,如果30秒前应该触发,那么quartz会正常的执行任务。对于延时的30秒,quartz不认为其misfired。相反,如果发现trigger比预期晚了61秒-那么被指定的misfire handler会处理这次misfire,并遵从指定的misfire instruction。出于测试的目的,我们设置misfireThreadshold为1000ms(1秒),可以快速测试misfiring。

不重复的simple trigger

在这个例子中,我们将看一下misfire在一次执行的trigger中是如何被处理的。

val trigger = newTrigger().
        startAt(DateUtils.addSeconds(new Date(), -10)).
        build()

同样trigger,是被显示的设置了misfire instruction的处理:

val trigger = newTrigger().
        startAt(DateUtils.addSeconds(new Date(), -10)).
        withSchedule(
            simpleSchedule().
                withMisfireHandlingInstructionFireNow()  //MISFIRE_INSTRUCTION_FIRE_NOW
            ).
        build()

为了测试,设置trigger在10秒前触发(当trigger被创建时已晚了10秒)。在现实中是不可能像上面那样调度tigger的。不要考虑trigger设置的正确性,相反要考虑scheduler已down掉了或者无多余的线程执行的情况。然而,quartz是如何处理这些意外的情况呢?在上面的第一段代码片断中,没有设置任何misfire instruction,所以采用的是默认的smart policy(智能的策略)。上面的第二个代码片断显示的指定了当misfire发生时我们期望的行为。来看下面的表格:

Instruction Meaning
smart policy - default 参考withMisfireHandlingInstructionFireNow
withMisfireHandlingInstructionFireNow 当发现misfire的时候,Job被立即执行。这就是smart policy。
例如:定义在上午两点的时候(2AM.)执行系统清理工作,不幸的是由于运维系统停掉了,在上午3点才恢复工作。这时,trigger发生了misfire,scheduler保存了这次misfire,并在3点的时候立即执行这次清理任务。
withMisfireHandlingInstructionIgnoreMisfires(QTZ-283) 参见withMisfireHandlingInstructionFireNow
withMisfireHandlingInstructionNextWithExistingCount 参见withMisfireHandlingInstructionNextWithRemainingCount
withMisfireHandlingInstructionNextWithRemainingCount 什么也不做,忽略misfired,这次被忽略的trigger也不会再次执行。
例如:trigger被指定用来录一个电视节目,但是当trigger misfired两小时,那就没有有开始录制点了(因为节止播完了,适用于使用此策略)。
withMisfireHandlingInstructionNowWithExistingCount 参考withMisfireHandlingInstructionFireNow
withMisfireHandlingInstructionNowWithRemainingCount 参考 withMisfireHandlingInstructionFireNow

Simple trigger 重复固定次数

这个情形稍有一点复杂。设想我们有一个执行固定次数的任务:

val trigger = newTrigger().
    startAt(dateOf(9, 0, 0)).
    withSchedule(
        simpleSchedule().
            withRepeatCount(7).
            withIntervalInHours(1).
            WithMisfireHandlingInstructionFireNow()  //or other
    ).
    build()

在这个例子中,从今天上午9点开始(startAt(dateOf(9, 0, 0)),trigger被每隔一小时触发,共8次(第一次执行+7次重复)。就是说,最后的一次执行要在下午4点时完成。然尔假设由于某些原因scheduler在上午9点到10点无法运行,但是发现的时候在上午的10:15,就是说有两个fire被misfired。这种情况下,scheduler如何调度呢?

instruction Meaning
smart policy - default 参见withMisfireHandlingInstructionNowWithExistingCount
withMisfireHandlingInstructionFireNow 参见withMisfireHandlingInstructionNowWithRemainingCount
MISFIRE_INSTRUCTION_FIRE_NOW 参见 withMisfireHandlingInstructionNowWithRemainingCount
withMisfireHandlingInstructionIgnoreMisfires(QTZ-283) 尽快触发发生misfire的trigger,然后回归正常调度。
例如:在我们的例子中,使用了这个策略,应该立即调度9点,10点的任务,在11点的时候进行正常的调度。
注:当处理misfire的时候,任务的实际执行时间可能晚于计划时间。这意味着,你不能只依赖当前系统时间,你需要使用:
1 JobExecutionContext .getScheduledFireTime():
2 def execute(context: JobExecutionContext) {
3 val date = context.getScheduledFireTime
4 //…
5 }
withMisfireHandlingInstructionNextWithExistingCount scheduler什么也不会做。相反,要等到下次调度时刻,运行所有的trigger。参见withMisfireHandlingInstructionNextWithRemainingCount。
例如:在10:15的时候Scheduler发现有两个trigger发生了misfire(错过了执行时刻)。那么等到下次执行时间点11点,就会触发未执行两个trigger继续执行,并在下午6点结束,原来是下午4点结束。
withMisfireHandlingInstructionNextWithRemainingCount Scheduler抛弃misfired的任务,并等待下一个调度时间继续执行。导致整个的执行次数会少于预期。也就是说执行6次,而不是8次。
例如:10:15发生misfired的两个任务被抛掉了。那在11点的时候会继续执行余下的任务,到4点完成。系统的行为像是misfire没有发生一样。
withMisfireHandlingInstructionNowWithExistingCount 第一个被misfire的trigger立即执行(不等下一次的触发条件),然后余下的就基于当前第一个misfire的任务的执行时间,以固定的时间间隔继续执行。这里改变的只是任务的调度执行时间,别的都没有改变。
Example scenario: at 10:15 the scheduler runs the first misfired execution. Then it waits 1 hour and fires the second one at 11:15 AM. All 8 executions are performed, the last one at 5:15 PM
withMisfireHandlingInstructionNowWithRemainingCount 第一个misfired的立即执行,其它misfired的抛弃。没有被misfired的继续按固定间隔执行。
Example scenario: at 10:15 the scheduler runs the first misfired execution (from 9 AM). It discards remaining misfired executions (the one from 10 AM) and waits 1 hour to execute six more triggers: 11:15, 12:15, … 4:15 PM

Simple trigger repeating infinitely

无限重复的任务。

val trigger = newTrigger().
    startAt(dateOf(9, 0, 0)).
    withSchedule(
        simpleSchedule().
            withRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY).
            withIntervalInHours(1).
            WithMisfireHandlingInstructionFireNow()  //or other
    ).
    build()

Once again trigger should fire on every hour, beginning at 9 AM today (startAt(dateOf(9, 0, 0)). However the scheduler was not capable of running jobs at 9 and 10 AM and it discovered that fact at 10:15 AM, i.e. 2 firings misfired. This is a more general situation compared to simple trigger running fixed number of times.

instructions Meaning
smart policy - default See: withMisfireHandlingInstructionNextWithRemainingCount
withMisfireHandlingInstructionFireNow See: withMisfireHandlingInstructionNowWithRemainingCount
withMisfireHandlingInstructionIgnoreMisfires(QTZ-283) The scheduler will immediately run all misfired triggers, then continue on schedule.
Example scenario: the triggers scheduled at 9 and 10 AM are executed immediately. Future invocations (next scheduled at 11 AM) are executed according to the plan.
withMisfireHandlingInstructionNextWithExistingCount See: withMisfireHandlingInstructionNextWithRemainingCount
withMisfireHandlingInstructionNextWithRemainingCount Does nothing, misfired executions are discarded. Then the scheduler waits for next scheduled interval and goes back to schedule.
Example scenario: Misfired execution at 9 and 10 AM are discarded. The first execution occurs at 11 AM.
withMisfireHandlingInstructionNowWithExistingCount See: withMisfireHandlingInstructionNowWithRemainingCount
withMisfireHandlingInstructionNowWithRemainingCount The first misfired execution is run immediately, remaining are discarded. Next execution happens after desired interval. Effectively the first execution time is moved to current time.
Example scenario: the scheduler fires misfired trigger immediately at 10:15 AM. Then waits an hour and runs the second one at 11:15 AM and continues with 1 hour interval.

CRON triggers

CRON triggers are the most popular ones amongst Quartz users. However there are also two other available triggers: DailyTimeIntervalTrigger (e.g. fire every 25 minutes) and CalendarIntervalTrigger (e.g. fire every 5 months). They support triggering policies not possible in both CRON and simple triggers. However they understand the same misfire handling instructions as CRON trigger.

val trigger = newTrigger().
 withSchedule(
  cronSchedule("0 0 9-17 ? * MON-FRI").
   withMisfireHandlingInstructionFireAndProceed()  //or other
 ).
 build()

In this example the trigger should fire every hour between 9 AM and 5 PM, from Monday to Friday. But once again first two invocations were missed (so the trigger misfired) and this situation was discovered at 10:15 AM. Note that available misfire instructions are different compared to simple triggers:

Instruction Meaning
smart policy - default See: withMisfireHandlingInstructionFireAndProceed
withMisfireHandlingInstructionIgnoreMisfires All misfired executions are immediately executed, then the trigger runs back on schedule.
Example scenario: the executions scheduled at 9 and 10 AM are executed immediately. The next scheduled execution (at 11 AM) runs on time.
withMisfireHandlingInstructionFireAndProceed(QTZ-283) Immediately executes first misfired execution and discards other (i.e. all misfired executions are merged together). Then back to schedule. No matter how many trigger executions were missed, only single immediate execution is performed.
Example scenario: the executions scheduled at 9 and 10 AM are merged and executed only once (in other words: the execution scheduled at 10 AM is discarded). The next scheduled execution (at 11 AM) runs on time.
withMisfireHandlingInstructionDoNothing All misfired executions are discarded, the scheduler simply waits for next scheduled time.
Example scenario: the executions scheduled at 9 and 10 AM are discarded, so basically nothing happens. The next scheduled execution (at 11 AM) runs on time.

Note: QTZ-283: MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY not working with JDBCJobStore - apparently there is a bug when JDBCJobStore is used, keep an eye on that issue.

As you can see various triggers behave differently based on the actual setup. Moreover, even though the so called smart policy is provided, often the decision is based on business requirements. Essentially there are three major strategies: ignore, run immediately and continue and discard and wait for next. They all have different use-cases:

Use ignore policies when you want to make sure all scheduled executions were triggered, even if it means multiple misfired triggers will fire. Think about a job that generates report every hour based on orders placed during that last hour. If the server was down for 8 hours, you still want to have that reports generated, as soon as you can. In this case the ignore policies will simply run all triggers scheduled during that 8 hour as fast as scheduler can. They will be several hours late, but will eventually be executed.

Use now* policies when there are jobs executing periodically and upon misfire situation they should run as soon as possible, but only once. Think of a job that cleans /tmp directory every minute. If the scheduler was busy for 20 minutes and finally can run this job, you don’t want to run in 20 times! One is enough, but make sure it runs as fast it can. Then back to your normal one-minute intervals.

Finally next* policies are good when you want to make sure your job runs at particular points in time. For example you need to fetch stock prices quarter past every hour. They change rapidly so if your job misfired and it is already 20 minutes past full hour, don’t bother. You missed the correct time by 5 minutes and now you don’t really care. It is better to have a gap rather than an inaccurate value. In this case Quartz will skip all misfired executions and simply wait for the next one.

猜你喜欢

转载自www.cnblogs.com/luohero/p/9366200.html