在应用运行中,任务错过触发的事情也是时有发生,比如,系统宕机重启,在关闭至重启的时间,任务可能Misfired;线程池线程满了,没有空闲线程执行任务,导致无法触发,等待超时造成Misfired;不允许并发任务(@DisallowConcurrentExecution
)在下次触发时间到达时,上次执行还未结束,只能等待,可能造成Misfired;触发器Trigger被暂停suspend时间段里,有些任务可能被Misfired等等。当然Quartz提供了一定的容忍度。配置文件可配置容忍错过触发阈值:org.quartz.jobStore.misfireThreshold=60000
,默认为1分钟。意味着本因触发的任务未能及时执行可以延迟1分钟执行。超过1分钟后,即认为任务“Misfired”。这时就需要有错过触发的调度策略来决定如何这些“Misfired”任务。
文章目录
错过触发处理策略
不同的触发器类型有自己的“Misfired”错过触发处理策略。可以查看各个触发器实现策略。
org.quartz.impl.triggers.AbstractTrigger.updateAfterMisfire(Calendar)
每个类型触发器要实现上述抽象类方法,进行“Misfired”任务处理。错过触发处理策略由MisfireHandler错过触发处理线程处理错过触发任务。总结如下:
序号 | 策略 | 值 | 描述 | 适用触发类型 |
---|---|---|---|---|
1 | MISFIRE_INSTRUCTION _IGNORE_MISFIRE_POLICY |
-1 | 这种策略会在资源适合的时候尽快的重新触发所有“Misfired”错过触发任务。不会影响现有的调度时间。例如,SimpleTrigger每15秒执行一次,而中间有5分钟时间它都“Misfired”了,一共错失了20个,5分钟后,假设资源充足了,并且任务允许并发,它会被一次性触发。 | 所有触发器 |
2 | MISFIRE_INSTRUCTION _SMART_POLICY |
0 | 触发器默认策略。如果当前触发器是CronTrigger则走序号【3】策略。如果当前触发器是SimpleTrigger,当重复次数为0时,走序号【5】策略;当重复次数为-1,即无限次数时,走序号【8】策略;否则走序号【6】策略。如果当前触发器是 CalendarIntervalTrigger,则走序号【3】策略。如果当前触发器是 DailyTimeIntervalTrigger,则走序号【3】策略。 |
所有触发器 |
3 | MISFIRE_INSTRUCTION _FIRE_ONCE_NOW |
1 | 立即触发“Misfired”错过触发任务。 | CronTrigger
CalendarIntervalTrigger DailyTimeIntervalTrigger
|
4 | MISFIRE_INSTRUCTION _DO_NOTHING |
2 | 不立即触发执行,等待下次触发时间到达时刻才触发执行,重新开始调度任务。 | CronTrigger
CalendarIntervalTrigger DailyTimeIntervalTrigger
|
5 | MISFIRE_INSTRUCTION _FIRE_NOW |
1 | 立即触发执行。此指令通常只适用于“一次性”(非重复)触发器。如果用于重复次数大于0的触发器,则走需要【7】策略 | SimpleTrigger |
6 | MISFIRE_INSTRUCTION _RESCHEDULE_NOW_WITH _EXISTING_REPEAT_COUNT |
2 | 以当前时间为开始触发时间,立即触发执行。重复时间间隔不变,重复次数更新为原先重复次数-已触发次数。然后已触发次数重置为0。如果当前时间是在结束时间之后,触发器将不会再次触发。该策略无法记住最初设置的“开始时间”和“重复次数” | SimpleTrigger |
7 | MISFIRE_INSTRUCTION _RESCHEDULE_NOW_WITH _REMAINING_REPEAT_COUNT |
3 | 与序号【6】策略一致,区别在于重复次数的计算,重复次数更新为原先重复次数-已触发次数-下次触发时间到当前时间区间触发的次数。 | SimpleTrigger |
8 | MISFIRE_INSTRUCTION _RESCHEDULE_NEXT_WITH _REMAINING_COUNT |
4 | 等待下次触发时间到达时刻才触发执行,重新开始调度任务。会更新已触发次数为已触发次数+下个触发时间到当前时间的下个触发时间区间的次数。 | SimpleTrigger |
9 | MISFIRE_INSTRUCTION _RESCHEDULE_NEXT_WITH _EXISTING_COUNT |
5 | 等待下次触发时间到达时刻才触发执行,重新开始调度任务。
|
SimpleTrigger |
错过触发处理策略测试
线程不足,相同时间同时触发多任务
测试结果:
当线程池满了,相同时间触发的任务只能等待有可用线程,或者非并发的任务上次触发执行太久,下次触发时间超过配置里设置的容忍错过触发阈值时,将被置为错过触发状态。由错过触发处理线程采用错过触发处理策略处理,或立即执行,或等待下一次触发等等,错过触发处理线程只会更新下次触发时间,并通知触发器监听器,最终还是由调度线程处理;如果没有超过阈值,则意味着已有可用线程可用,正常运行。因为线程池是按触发器的触发时间,然后才是触发器优先级来排任务队列的。一种错过触发任务一直等待的情形,只能是线程不足,而且线程都执行了长时间、甚至是死循环不会任务的这种情形。而线程饥饿是JVM进程调度的范畴,而Quartz调度处理线程中调度任务使用的线程池是SimpleThreadPool,默认的线程优先级是5,每个线程都是有机会参与CPU竞争的。而且Java优先级较高的线程不一定每一次都优先执行,有一定随机性。
测试步骤:
1、配置文件中设置任务线程池大小为1。改造FirstJob让它休眠60s。
2、还是定点相同时间触发多个任务。FistJob优先级最高优先执行。
3、SimpleJob和HelloJob处于等待状态。等待FirstJob线程结束时,另外两线程才相继触发。org.quartz.jobStore.misfireThreshold: 60000 设置容忍错过触发阈值为60s。如:SimpleJob本因55分触发的,如果56分还未触发,就会错过触发。由于线程池满了,SimpleJob只能等待FirstJob执行结束(即休眠60s)。60s后,FirstJob刚刚执行完毕,此时刚好在容忍错过触发阈值,错过触发。由MisfireHandler错过触发线程根据错过触发策略处理。如下图,处理了两个错过触发的任务。(这时候的策略是立即处理)
4、将容忍错过触发阈值加大为61s。即:org.quartz.jobStore.misfireThreshold: 61000
FirstJob线程结束后。其他两个任务此时未超过容忍错过触发阈值。按触发优先级争用线程池,正常处理。
CronTrigger处理策略
使用Cron表达式,创建的触发器为CronTrigger,他采用的默认策略:
MISFIRE_INSTRUCTION_SMART_POLICY。对于CronTrigger,采用策略即立即触发。
而CronTrigger暴露的错过触发策略:
即对应表格:
CronTrigger方法 | 对应策略 |
withMisfireHandlingInstructionIgnoreMisfires | MISFIRE_INSTRUCTION_IGNORE_ MISFIRE_POLICY 序号【1】 |
withMisfireHandlingInstructionFireAndProceed | MISFIRE_INSTRUCTION_FIRE_ONCE _NOW 序号【3】 |
withMisfireHandlingInstructionDoNothing | MISFIRE_INSTRUCTION_DO_NOTHING 序号【4】 |
观察其实现类org.quartz.impl.triggers.CronTriggerImpl,默认策略就是立即触发。
1、测试withMisfireHandlingInstructionDoNothing 即序号【4】策略。
创建触发器,带有withMisfireHandlingInstructionDoNothing
2、设置容忍错过触发阈值为60s,让任务错过触发。改下触发时间,继续测试。
3、根据打印日志可知,错过触发处理线程已经处理错过触发任务。但此时错过触发任务没有立即执行,要等待下一次执行。(下次执行时间是下一天对于的时间点,无法重现)
SimpleTrigger处理策略
1、测试SimpleTrigger的错过触发策略,定义StatefulDumbJob任务类,使用有状态和并发控制的Job,【有状态和并发Job测试】。将容忍错过触发阈值设为7000ms。线程数为2。
2、定义两个任务,都是休眠10s,调度启动15s后,开始任务,后每3s触发,无限重复执行。Job1使用默认策略,因为定义的触发器SimpleTrigger及无限重复,由序号【1】策略,可知,最终走序号【8】策略
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT;job2使用序号【6】策略MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
3、查看测试结果,05分15秒开始两个任务触发。下个触发时间按周期3s,本应18s触发,但任务休眠10s后才能执行,刚好超过容忍错过触发的阈值,变成“Misfired”。由错过触发处理线程检测处理,05分27秒处理错过触发任务。发现job2立即执行,但job1在05分30秒才执行。第二次处理错过触发任务时,job2立即执行,但job1在05分42秒才执行。05分30秒、05分42秒、05分57秒都落在05分15秒开始,每3秒的周期点上,即会等待检测当前时间的下个触发时间触发错过触发任务。验证了序号【8】和【6】策略。
代码参见
不同类型触发器定义了不同的公有静态常量来表示错过触发指令,用于错过触发任务处理,过触发指令可由以下接口找到
org.quartz.SimpleTrigger
org.quartz.CronTrigger
org.quartz.CalendarIntervalTrigger
org.quartz.DailyTimeIntervalTrigger
不同类型触发器实现AbstractTrigger抽象方法进行错过触发任务处理
org.quartz.impl.triggers.AbstractTrigger.updateAfterMisfire(Calendar)
总结
Quartz定义了容忍错过触发阈值和错过触发处理策略,不同类型的触发器可以有不同的触发策略,根据可能错过触发的场景,配置线程池大小也选的比较重要了,Quartz配置应该要能适应应用业务需要,动态调整,比如,可以上配置中心,等等。
发表评论