Quartz Cron触发器未遵循Misfire策略问题求助
问题描述
我们通过工厂方法在后台库中创建Quartz触发器,核心代码如下:
public Trigger buildTrigger(final JobDescription jobDescription) { final JobDescription.TriggerType triggerType = jobDescription.getTriggerType(); if (triggerType == null) { throw new IllegalArgumentException("trigger type can not be empty"); } return switch (triggerType) { case CRON -> makeCronTrigger(jobDescription); case ONCE -> makeOnceTrigger(jobDescription); case MULTIPLE_TIMES -> makeMultipleTimesTrigger(jobDescription); case LOOP -> makeLoopTrigger(jobDescription); }; } private CronScheduleBuilder createCronSchedule(JobDescription jobDescription) { CronScheduleBuilder cronSchedule = null; MisfirePolicy misfirePolicy = jobDescription.getMisfirePolicy() != null ? jobDescription.getMisfirePolicy() : MisfirePolicy.None; switch (misfirePolicy) { case DoNothing: cronSchedule = cronSchedule(jobDescription.getCronExpression()).withMisfireHandlingInstructionDoNothing(); break; case FireAndProceed: cronSchedule = cronSchedule(jobDescription.getCronExpression()).withMisfireHandlingInstructionFireAndProceed(); break; case IgnoreMisfires: cronSchedule = cronSchedule(jobDescription.getCronExpression()).withMisfireHandlingInstructionIgnoreMisfires(); break; default: cronSchedule = cronSchedule(jobDescription.getCronExpression()); } return cronSchedule; } private Trigger makeCronTrigger(final JobDescription jobDescription) { final TriggerBuilder<CronTrigger> cronTriggerTriggerBuilder = newTrigger() .withIdentity(jobDescription.getJobId()) .withSchedule(createCronSchedule(jobDescription)) .withPriority(getPriorityValue(jobDescription)); setIdentity(jobDescription, cronTriggerTriggerBuilder); return cronTriggerTriggerBuilder.build(); }
功能微服务中构建JobDescription的代码:
@Bean @ConditionalOnProperty(prefix = "tickets.background.job", name = "deliver-tickets.enabled", havingValue = "true") JobDescription deliverTicketsJobDescription() { final long now = metrics ? System.currentTimeMillis() : 0; JobDescription jobDescription = JobDescriptionBuilder.builder() .triggerType(TriggerType.CRON) .jobClass(DeliverTicketsJob.class) .cronExpression(deliverTicketsCron) // 0/5 * * * * ? .misfirePolicy(MisfirePolicy.FireAndProceed) .jobGroupId(jobGroupId) .jobId("deliverTickets") .build(); if (metrics) { LOGGER.debug("METRIC: deliverTicketsJobDescription ~ duration: " + (System.currentTimeMillis() - now) + "ms"); } return jobDescription; }
同时设置了misfireThreshold为1秒:
public class TicketApplication { static { System.setProperty("quartz.jobStore.misfireThreshold", "1000"); } }
已确认qrtz_triggers表中misfire_instruction字段配置正确,但所有misfire策略均未按预期生效,表现统一为:
- 启动时,遗漏的任务会立即连续执行两次,之后恢复每5秒一次的正常调度;
- 通过断点或
Thread.sleep延迟任务20秒后,恢复时任务会连续执行多次直至追上进度,之后回到正常调度。
排查思路与解决方案
1. 确认misfireThreshold配置是否生效
通过System.setProperty设置的配置可能被Quartz的配置文件(如quartz.properties、Spring Boot的application.yml)覆盖,需验证实际生效值:
- 查看Quartz启动日志,搜索包含
misfireThreshold的配置项,确认值为1000; - 通过调试获取JobStore实例,检查
misfireThreshold属性值是否正确。
2. 检查Job的并发执行配置
默认情况下Quartz允许任务并发执行,此时任务阻塞时后续触发会直接启动新线程,不会触发misfire逻辑。需在DeliverTicketsJob类上添加@DisallowConcurrentExecution注解,确保同一时间仅一个任务实例运行,此时错过的触发才会被标记为misfire,策略才会生效。
3. 验证Cron表达式的特殊处理影响
对于0/5 * * * * ?这类短间隔Cron表达式,Quartz可能存在智能合并触发的逻辑。可先尝试使用更长间隔的表达式(如每分钟触发一次)测试misfire策略是否正常,排除短间隔的特殊处理干扰。
4. 检查Quartz版本兼容性
旧版本Quartz(如2.2.x及之前)存在CronTrigger misfire策略的已知bug,尤其是JDBCJobStore场景下。建议升级至最新稳定版本(如2.3.x+),验证问题是否解决。
5. 避免触发器重复创建
若应用启动时重复调度触发器,会覆盖数据库中已有的配置,导致misfire策略被重置。需检查调度逻辑,确保触发器仅在首次创建或配置变更时被调度。
6. 调试misfire逻辑
通过调试CronTrigger的updateAfterMisfire方法,确认Quartz是否正确识别misfire场景并应用对应策略,定位是策略未被应用还是misfire未被识别的问题。
内容的提问来源于stack exchange,提问作者owen gerig

