Quartz暂停恢复后执行积压任务问题及方案隐患咨询
Quartz定时推送任务暂停恢复问题解决方案
问题背景
未使用数据库持久化,基于Quartz实现定时推送任务,推送数据的位置指针存储在JobDataMap中。遇到的问题是:任务暂停一段时间后恢复,Quartz会快速执行暂停期间遗漏的所有任务,导致数据批量推送到前端。
曾尝试暂停时删除任务,删除前获取位置指针,恢复时重新创建任务,但删除操作与任务执行属于不同线程,无法保证获取的指针是最新状态,存在已推送数据重复推送的风险。
现有代码
添加任务实现
@Override public int addJob(JobInfo jobInfo) { int result = 0; int isJobExist = this.isJobExist(JobKey.jobKey(jobInfo.getJobName(), jobInfo.getJobGroup())); if (isJobExist == 1) { result = -1; log.info("Task already exists"); } else { try { JobDetail jobDetail = null; if (isJobExist == 0) { jobDetail = scheduler.getJobDetail(JobKey.jobKey(jobInfo.getJobName(), jobInfo.getJobGroup())); } else if (isJobExist == -1) { jobDetail = JobBuilder.newJob( (Class<? extends QuartzJobBean>) Class.forName(jobInfo.getJobClassname())) .withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup()) .withDescription(jobInfo.getDescription()) .storeDurably(jobInfo.isDurability()).build(); if(jobInfo.getNpcParam() != null){ jobDetail.getJobDataMap().put(NpcParam.NPC_JOB_PARAM, jobInfo.getNpcParam()); } } // 空cron表达式创建单次任务,否则创建周期任务 if (!StringUtils.isEmpty(jobInfo.getCronExpression())) { CronTrigger cronTrigger = TriggerBuilder.newTrigger() .withIdentity(jobInfo.getTriggerName(), jobInfo.getTriggerGroup()) .withSchedule(CronScheduleBuilder.cronSchedule(jobInfo.getCronExpression())) .build(); scheduler.scheduleJob(jobDetail, cronTrigger); } else { Trigger trigger = TriggerBuilder.newTrigger() .withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup()) .startAt(sdf.parse(jobInfo.getNextFireTime())) .withSchedule(SimpleScheduleBuilder.simpleSchedule().withRepeatCount(0)) .build(); scheduler.scheduleJob(jobDetail, trigger); } } catch (ClassNotFoundException e) { result = 1; log.error("The Class corresponding to the task does not exist!"); } catch (SchedulerException e) { result = 2; e.printStackTrace(); log.error("task scheduling failed"); } catch (ParseException e) { result = 3; log.error("time conversion error"); } } return result; }
暂停/恢复任务代码
// 暂停任务 scheduler.pauseJob(JobKey.jobKey(jobName, jobGroup)); // 恢复任务 scheduler.resumeJob(JobKey.jobKey(jobName, jobGroup));
解决方案建议
1. 配置CronTrigger的错过触发处理策略
Quartz的CronTrigger默认会执行所有错过的任务,只需在创建Trigger时指定withMisfireHandlingInstructionDoNothing(),即可直接跳过暂停期间的所有遗漏任务,恢复后按Cron表达式正常执行下一次任务:
CronTrigger cronTrigger = TriggerBuilder.newTrigger() .withIdentity(jobInfo.getTriggerName(), jobInfo.getTriggerGroup()) .withSchedule(CronScheduleBuilder.cronSchedule(jobInfo.getCronExpression()) // 跳过所有错过的触发任务 .withMisfireHandlingInstructionDoNothing()) .build();
该方案无需修改任务生命周期,是解决批量执行遗漏任务问题最直接的方式。
2. 优化位置指针的线程安全存储
若需确保位置指针的准确性,不要仅依赖JobDataMap,改用线程安全的存储方案:
- 使用
ConcurrentHashMap存储任务的位置指针,任务执行时更新指针,获取时直接从Map读取,避免多线程下的状态不一致问题。 - 若需要持久化指针,可使用带锁的本地文件存储,执行任务时加锁更新,删除/恢复任务时加锁读取,保证获取的指针是最新状态。
3. 暂停时动态调整Trigger的下次触发时间
不直接暂停任务,而是修改Trigger的下次触发时间为恢复后的合理时间点:
// 暂停时,获取当前Trigger并更新下次触发时间 Trigger trigger = scheduler.getTrigger(TriggerKey.triggerKey(triggerName, triggerGroup)); if (trigger instanceof CronTrigger) { CronTrigger cronTrigger = (CronTrigger) trigger; // 计算恢复后的下一个合法触发时间 Date nextFireTime = cronTrigger.getScheduleBuilder().build().computeFirstFireTime(null); // 更新Trigger的下次触发时间 Trigger newTrigger = cronTrigger.getTriggerBuilder() .startAt(nextFireTime) .build(); scheduler.rescheduleJob(trigger.getKey(), newTrigger); }
这种方式通过动态调整任务执行计划,从根源上避免了暂停恢复后执行遗漏任务的问题。
内容的提问来源于stack exchange,提问作者HuaJFrame
相关产品推荐
相关产品推荐

