Quartz的misfire有什么玄机?

1,647 阅读3分钟

背景

在公司遇到关于Quartzmisfire问题, 本应04:32触发的任务没有触发,这偏偏又是一个十分重要的任务,还好之前设计的时候留有应急兜底,从而避免大问题.但回到问题本身,为什么用Quartz设定04:32的任务没有触发呢,难道Quartz有bug?这基本上不可能,遇到问题先从自身找原因,且听我娓娓道来

日志分析

通过日志分析,发现04:30-05:45之间的任务触发数量大大小于其他时间段,在这75分钟内数据库的耗时明显增加,最终导致任务积累而misfire,后来通过DBA了解到这75分钟与某个数据库任务恰好吻合,猜测这是根本原因.

配置

虽然根本原因不是应用引起,但应用对这些特殊情况应该具有相当的容错性,不能因为数据库的慢响应产生预期外的结果.问题在于misfire,我们程序配置misfire的容忍时间是60秒,misfire策略为DO_NOTHING,意思即误点超过60秒的任务被认定为misfire,且直接忽略不做任何事情,等待下一次触发.这就是原定04:32触发任务被忽略的原因.

misfire

不同的Trigger有不同的触发策略,我们只用了CronTrigger,因此下面只说CronTrigger的三种misfire策略

misfire策略含义
DO_NOTHING不管misfire,等待下一次触发,这对于频繁触发的任务来说不算什么,但如果间隔非常大或者少触发一次会造成重要影响的任务,不能采用这种方式
IGNORE_MISFIRE_POLICY忽略misfire的处理,依然按照错过的频率触发,例如已经错过了3次,只要符合条件就能会补触发这3次
FIRE_ONCE_NOW(SMART_POLICY默认)这是默认的策略,如果错过了3次触发,会立刻补一次触发(当然要满足空闲线程等条件),之后按照原定的触发频率,适用大多数场景,包括本文的问题

对于本文的问题,将该Trigger的misfire策略调整为FIRE_ONCE_NOW,并且提高优先级应该能解决问题,至少不会跳过本应触发的任务

并发

在查阅官方文档的时候,也读到关于并发的内容,默认的Job class都是允许并发的,通过添加@DisallowConcurrentExecution能够禁止Job实例的并发,这个注解是加在Job class上的,但一个Job class不等于一个Job实例,一个Job class能产生多个JobDetail,每一个JobDetail才是一个Job实例,这是一个容易犯错的误区.

插件

Quartz提供了很多钩子和插件来拓展功能,这部分在百度是基本上看不到的,强烈建议大家一定阅读官方文档关于插件的章节,在这里介绍官方的TriggerHistory插件,方便跟踪和排查Trigger的问题

org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin

只要配上此插件,每次fire, complete,misfire都会打印日志,方便排查

Trigger groupName.sleepJobTrigger fired job groupName.sleepJob at:  16:48:20 07/25/2021
Trigger groupName.sleepJobTrigger completed firing job groupName.sleepJob at  16:48:22 07/25/2021 with resulting trigger instruction code: DO NOTHING
Trigger groupName.fireJobTrigger misfired job groupName.fireJob  at:  16:48:22 07/25/2021.  Should have fired at:  16:48:17 07/25/2021