Quartz 在misfire模式[错失、补偿执行] 策略

本文详细解析了Quartz调度器中不同Misfire处理策略的原理与应用场景,包括MISFIRE_INSTRUCTION_FIRE_ONCE_NOW、MISFIRE_INSTRUCTION_DO_NOTHING及MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY等,对比了CronTrigger与SimpleSchedule的Misfire处理差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

调度(scheduleJob)或恢复调度(resumeTrigger,resumeJob)后不同的misfire对应的处理规则

misfire产生的条件是:到了该触发执行时上一个执行还未完成,且线程池中没有空闲线程可以使用(或有空闲线程可以使用但job设置为@DisallowConcurrentExecution)且过期时间已经超过misfireThreshold就认为是misfire了,错失触发了

job默认是并发执行, 如果job设置@DisallowConcurrentExecution ,则为串行执行.
 

misfired job原因

  1. 系统因为某些原因被重启。在系统关闭到重新启动之间的一段时间里,可能有些任务会被 misfire;
  2. Trigger 被暂停(suspend)的一段时间里,有些任务可能会被 misfire;
  3. 线程池中所有线程都被占用,导致任务无法被触发执行,造成 misfire;
  4. 有状态任务在下次触发时间到达时,上次执行还没有结束;为了处理 misfired job,Quartz 中为 trigger 定义了处理策略,主要有下面两种:MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:针对 misfired job 马上执行一次;MISFIRE_INSTRUCTION_DO_NOTHING:忽略 misfired job,等待下次触发;默认是MISFIRE_INSTRUCTION_SMART_POLICY,该策略在CronTrigger中=MISFIRE_INSTRUCTION_FIRE_ONCE_NOW线程默认1分钟执行一次;在一个事务中,默认一次最多recovery 20个;

 

 

 

参数设置

quartz.properties

org.quartz.jobStore.misfireThreshold = 60000 [默认]

CronSchedule 策略

org.quartz.Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .withSchedule(
                        CronScheduleBuilder.cronSchedule("0/3 * * * * ?")

                        //  withMisfireHandlingInstructionIgnoreMisfires() MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY 忽略所有的超时状态,按照触发器的策略执行。
                        // withMisfireHandlingInstructionFireAndProceed()   MISFIRE_INSTRUCTION_FIRE_ONCE_NOW   立刻执行一次,然后就按照正常的计划执行。
                        // withMisfireHandlingInstructionDoNothing()   MISFIRE_INSTRUCTION_DO_NOTHING           目前不执行,然后就按照正常的计划执行。
                )
                .build();

 

withMisfireHandlingInstructionFireAndProceed   [MISFIRE_INSTRUCTION_FIRE_ONCE_NOW](默认)
——以当前时间为触发频率立刻触发一次执行
——然后按照Cron频率依次执行



withMisfireHandlingInstructionDoNothing   [MISFIRE_INSTRUCTION_DO_NOTHING ]
——不触发立即执行
——等待下次Cron触发频率到达时刻开始按照Cron频率依次执行


withMisfireHandlingInstructionIgnoreMisfires    [MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY] 
——以错过的第一个频率时间立刻开始执行
——重做错过的所有频率周期后
——当下一次触发频率发生时间大于当前时间后,再按照正常的Cron频率依次执行
即: 忽略所有的超时状态,按照触发器的策略执行。

 

前提:在每个星期周一下午5点到7点,每隔一个小时执行一次,理论上共执行3次。

1. 情景一:

//所有misfire的任务会马上执行
public static final int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1;
若是5点misfire,6:15系统恢复之后,5,6点的misfire会马上执行。

2. 情景二:


//不做任何事情
public static final int MISFIRE_INSTRUCTION_DO_NOTHING = 2;
若是5点misfire,6:15系统恢复之后,只会执行7点的misfire。如果下次执行时间超过了end time,实际上就没有执行机会了。

3. 情景三:(cronTrigger的默认策略)


// CornTrigger默认策略,合并部分misfire,正常执行下一个周期的任务。
public static final int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1;
若是5点misfire,6:15系统恢复之后,立刻执行一次(只会一次)misfire。

 

SimpleSchedule 策略

SimpleScheduleBuilder sp = SimpleScheduleBuilder.simpleSchedule();
 
sp.withMisfireHandlingInstructionFireNow();
sp.withMisfireHandlingInstructionIgnoreMisfires();
sp.withMisfireHandlingInstructionNextWithExistingCount();
sp.withMisfireHandlingInstructionNextWithRemainingCount();
sp.withMisfireHandlingInstructionNowWithExistingCount();  (默认)
sp.withMisfireHandlingInstructionNowWithRemainingCount();

 

withMisfireHandlingInstructionNowWithExistingCount(默认)
——以当前时间为触发频率立即触发执行
——执行至FinalTIme的剩余周期次数
——以调度或恢复调度的时刻为基准的周期频率,FinalTime根据剩余次数和当前时间计算得到
——调整后的FinalTime会略大于根据starttime计算的到的FinalTime值


withMisfireHandlingInstructionFireNow
——以当前时间为触发频率立即触发执行
——执行至FinalTIme的剩余周期次数
——以调度或恢复调度的时刻为基准的周期频率,FinalTime根据剩余次数和当前时间计算得到
——调整后的FinalTime会略大于根据starttime计算的到的FinalTime值



withMisfireHandlingInstructionIgnoreMisfires
——以错过的第一个频率时间立刻开始执行
——重做错过的所有频率周期
——当下一次触发频率发生时间大于当前时间以后,按照Interval的依次执行剩下的频率
——共执行RepeatCount+1次


withMisfireHandlingInstructionNextWithExistingCount
——不触发立即执行
——等待下次触发频率周期时刻,执行至FinalTime的剩余周期次数
——以startTime为基准计算周期频率,并得到FinalTime
——即使中间出现pause,resume以后保持FinalTime时间不变


withMisfireHandlingInstructionNextWithRemainingCount
——不触发立即执行
——等待下次触发频率周期时刻,执行至FinalTime的剩余周期次数
——以startTime为基准计算周期频率,并得到FinalTime
——即使中间出现pause,resume以后保持FinalTime时间不变




withMisfireHandlingInstructionNowWithRemainingCount
——以当前时间为触发频率立即触发执行
——执行至FinalTIme的剩余周期次数
——以调度或恢复调度的时刻为基准的周期频率,FinalTime根据剩余次数和当前时间计算得到
——调整后的FinalTime会略大于根据starttime计算的到的FinalTime值


MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
——此指令导致trigger忘记原始设置的starttime和repeat-count
——触发器的repeat-count将被设置为剩余的次数
——这样会导致后面无法获得原始设定的starttime和repeat-count值


 

 

 

 

 

 

 

参考:

https://www.cnblogs.com/skyLogin/p/6927629.html

https://www.jianshu.com/p/572322b36383

 

 

<think>嗯,用户这次想深入了解SpringQuartz集群模式的并行执行机制。之前已经解释了集群模式下如何避免重复执行的问题,现在用户显然是想利用集群特性提升任务处理能力。用户可能是个中高级开发者,正在设计分布式任务调度系统。从他的提问方式看,已经掌握了基本集群配置,现在更关注性能优化层面的实现。不过要注意区分“集群节点间的并行”和“单节点内的多线程并行”这两个不同维度。先从基础机制开始梳理:Quartz集群的并行本质上是数据库驱动的协同工作。核心原理在于每个节点都是独立调度器,通过数据库锁协调触发权。当多个节点同时竞争一个任务时,只有获取行锁的节点能执行,这确保了安全性,但默认情况下也限制了并行度。要实现真正并行需要分两个层面解决:首先是跨节点并行。这取决于业务设计,需要将大任务拆分为多个可并行的子任务。比如按日期分片或按业务ID哈希,让不同节点处理不同数据分片。这时Job必须实现StatefulJob接口避免状态冲突[^3]。其次是单节点内的线程池优化。用户可能没意识到线程池配置对并行能力的影响。配置参数如quartz.threadPool.threadCount直接影响单个节点能同时执行的任务数。如果设置太小,即使多个节点也会阻塞。数据库性能瓶颈也要特别注意。在highcon场景下,大量节点竞争锁可能导致数据库连接耗尽。建议配置适当的锁超时(misfireThreshold)并启用c3p0连接池优化[^1]。用户可能还忽略了SchedulerFactoryBean的高级配置。通过设置org.quartz.scheduler.batchTriggerAcquisitionMaxCount可以控制单次拉取的触发数量,这在处理突发任务流时对并行吞吐量很关键。最后需要提醒故障处理机制。并行环境下某个子任务失败不应影响其他分片。建议在Job实现中加入健壮的重试逻辑,并注意Misfire策略选择(如withMisfireHandlingInstructionIgnoreMisfires)。相关扩展问题会涉及更细节的性能调优方向,比如监控锁竞争时间的方法,或是优化数据库索引的具体策略。这些都可能成为用户后续关注的痛点。</think> 以下是Spring Quartz集群模式下**并行执行机制**的核心原理与配置实现: --- ### ⚙️ 集群并行执行的两级机制 #### 1. **跨节点并行 (天然分布式)** 通过数据库锁实现任务的分片调度: ```java org.quartz.jobStore.isClustered = true // 启用集群[^1] ``` - **自动任务分片**:不同节点通过竞争数据库锁获取不同任务实例的执行权 - **负载均衡逻辑**: $$ P_i = \frac{1}{N} \sum_{k=1}^{N} (task_k \mod N == i) $$ 其中$N$为节点数,$P_i$为节点$i$分配到的任务概率 #### 2. **单节点并行 (线程池优化)** 配置线程池实现节点内并发: ```properties # 节点内并行度 (关键参数) org.quartz.threadPool.threadCount = 20 org.quartz.jobStore.maxMisfiresToHandleAtATime = 20 ``` 此配置使单个节点可**同时处理20个任务**[^2] --- ### 🔧 实现步骤(Spring Boot配置示例) ```yaml spring: quartz: properties: org.quartz.scheduler.instanceId: AUTO org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.isClustered: true # 启用集群 org.quartz.jobStore.clusterCheckinInterval: 15000 org.quartz.threadPool.threadCount: 20 # 单节点并发数 ``` ```java @Bean public JobDetail jobDetail() { return JobBuilder.newJob(MyParallelJob.class) .storeDurably() .build(); } @Bean public Trigger trigger() { return TriggerBuilder.newTrigger() .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(10) .repeatForever()) .build(); } ``` --- ### 📊 并行能力对照表 | 场景 | 配置要点 | 并行效果 | |----------------------|----------------------------|-------------------------| | 节点间任务分配 | `isClustered=true` | $N$个节点并行执行不同任务 | | 单节点高并发 | `threadPool.threadCount=K` | 1个节点并行$K$个任务 | | 突发流量处理 | `maxMisfiresToHandleAtATime=M` | 单次处理$M$个积压任务 | --- ### ⚠️ 注意事项 1. **避免任务冲突**:实现`StatefulJob`接口的任务无法并行,需使用无状态`Job`接口 2. **数据库瓶颈**:节点数$ > $50时需优化数据库连接池[^3] 3. **时钟同步**:集群节点必须保持时间同步(误差$<$1秒) --- ### 🌰 实践案例 ```java public class ParallelJob implements Job { @Override public void execute(JobExecutionContext ctx) { // 获取当前任务分片参数 ShardingContext shard = (ShardingContext) ctx.getMergedJobDataMap().get("shard"); // 按分片参数处理数据子集 processData(shard.getShardIndex(), shard.getTotalShards()); } } ``` > 通过分片参数实现**数据并行**处理(如按ID范围分割数据集) --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值