作业调度框架 Quartz 学习笔记(五) -- 错过的任务怎么办?

时间:2022-09-18 08:07:17
10050人阅读 评论(2) 收藏 举报

不知道大家在用Quartz的时候 有没有遇到这样一种情况:

触发器设定每3秒钟触发一次 ,但是工作需要10秒钟的执行时间.因此,在一次任务结束执行前,触发器已经错失触发

当这种情况下我们怎么处理呢,让我们一起学习一下......

还是先贴代码:

job类:StatefulDumbJob.java

  1. import java.text.SimpleDateFormat;  
  2. import java.util.Calendar;  
  3. import org.quartz.DisallowConcurrentExecution;  
  4. import org.quartz.Job;  
  5. import org.quartz.JobDataMap;  
  6. import org.quartz.JobExecutionContext;  
  7. import org.quartz.JobExecutionException;  
  8. import org.quartz.PersistJobDataAfterExecution;  
  9.   
  10. @PersistJobDataAfterExecution  
  11. @DisallowConcurrentExecution  
  12. public class StatefulDumbJob implements Job {  
  13.   
  14.     // 静态常量,作为任务在调用间,保持数据的键(key)  
  15.     // NUM_EXECUTIONS,保存的计数每次递增1  
  16.     // EXECUTION_DELAY,任务在执行时,中间睡眠的时间。本例中睡眠时间过长导致了错失触发  
  17.     public static final String NUM_EXECUTIONS = "NumExecutions";  
  18.     public static final String EXECUTION_DELAY = "ExecutionDelay";  
  19.   
  20.     @Override  
  21.     public void execute(JobExecutionContext context)  
  22.             throws JobExecutionException {  
  23.   
  24.         // 任务执行的时间  
  25.         SimpleDateFormat dateFormat = new SimpleDateFormat(  
  26.                 "yyyy-MM-dd HH:mm:ss");  
  27.         String jobRunTime = dateFormat.format(Calendar.getInstance().getTime());  
  28.   
  29.         System.err.println("---" + context.getJobDetail().getKey().getName() + " 在  : ["  
  30.                 + jobRunTime + "] 执行了!!");  
  31.   
  32.         // 任务执行计数 累加  
  33.         JobDataMap map = context.getJobDetail().getJobDataMap();  
  34.         int executeCount = 0;  
  35.         if (map.containsKey(NUM_EXECUTIONS)) {  
  36.             executeCount = map.getInt(NUM_EXECUTIONS);  
  37.         }  
  38.         executeCount++;  
  39.         map.put(NUM_EXECUTIONS, executeCount);  
  40.   
  41.         // 睡眠时间: 由调度类重新设置值 ,本例为 睡眠10s  
  42.         long delay = 5000l;  
  43.         if (map.containsKey(EXECUTION_DELAY)) {  
  44.             delay = map.getLong(EXECUTION_DELAY);  
  45.         }  
  46.   
  47.         try {  
  48.             Thread.sleep(delay);  
  49.         } catch (Exception ignore) {  
  50.         }  
  51.   
  52.         // 睡眠醒来后,打印任务执行结束的信息  
  53.         System.err.println("  -" + context.getJobDetail().getKey().getName()  
  54.                 + " 完成次数  : " + executeCount );  
  55.     }  
  56. }  

调度类: MisfireExample.java

  1. import static org.quartz.DateBuilder.nextGivenSecondDate;  
  2. import static org.quartz.JobBuilder.newJob;  
  3. import static org.quartz.SimpleScheduleBuilder.simpleSchedule;  
  4. import static org.quartz.TriggerBuilder.newTrigger;  
  5.   
  6. import java.text.SimpleDateFormat;  
  7. import java.util.Date;  
  8.   
  9. import org.quartz.JobDetail;  
  10. import org.quartz.Scheduler;  
  11. import org.quartz.SchedulerFactory;  
  12. import org.quartz.SchedulerMetaData;  
  13. import org.quartz.SimpleTrigger;  
  14. import org.quartz.impl.StdSchedulerFactory;  
  15.   
  16. public class MisfireExample {  
  17.   
  18.     public static void main(String[] args) throws Exception {  
  19.         MisfireExample example = new MisfireExample();  
  20.         example.run();  
  21.     }  
  22.   
  23.     public void run() throws Exception {  
  24.         // 任务执行的时间 格式化  
  25.         SimpleDateFormat dateFormat = new SimpleDateFormat(  
  26.                 "yyyy-MM-dd HH:mm:ss");  
  27.   
  28.         SchedulerFactory sf = new StdSchedulerFactory();  
  29.         Scheduler sched = sf.getScheduler();  
  30.         System.out.println("--------------- 初始化 -------------------");  
  31.   
  32.         // 下一个第15秒  
  33.         Date startTime = nextGivenSecondDate(null15);  
  34.   
  35.         // statefulJob1 每3s运行一次,但它会延迟10s  
  36.         JobDetail job = newJob(StatefulDumbJob.class)  
  37.                 .withIdentity("statefulJob1""group1")  
  38.                 .usingJobData(StatefulDumbJob.EXECUTION_DELAY, 10000L) // 设置参数:睡眠时间 10s  
  39.                 .build();  
  40.         SimpleTrigger trigger = newTrigger()  
  41.                 .withIdentity("trigger1""group1")  
  42.                 .startAt(startTime)  
  43.                 .withSchedule(  
  44.                         simpleSchedule().withIntervalInSeconds(3)  
  45.                                 .repeatForever()).build();  
  46.         Date ft = sched.scheduleJob(job, trigger);  
  47.         System.out.println(job.getKey().getName() + " 将在: " + dateFormat.format(ft)  
  48.                 + "  时运行.并且重复: " + trigger.getRepeatCount() + " 次, 每次间隔 "  
  49.                 + trigger.getRepeatInterval() / 1000 + " 秒");  
  50.   
  51.         // statefulJob2 将每3s运行一次 , 但它将延迟10s , 然后不断的迭代  
  52.         job = newJob(StatefulDumbJob.class)  
  53.                 .withIdentity("statefulJob2""group1")  
  54.                 .usingJobData(StatefulDumbJob.EXECUTION_DELAY, 10000L)// 设置参数:睡眠时间 10s  
  55.                 .build();  
  56.         trigger = newTrigger()  
  57.                 .withIdentity("trigger2""group1")  
  58.                 .startAt(startTime)  
  59.                 .withSchedule(  
  60.                         simpleSchedule()  
  61.                                 .withIntervalInSeconds(3)  
  62.                                 .repeatForever()  
  63.                                  // 设置错失触发后的调度策略   
  64.                                 .withMisfireHandlingInstructionNowWithRemainingCount()  
  65.                                 )   
  66.                 .build();  
  67.   
  68.         ft = sched.scheduleJob(job, trigger);  
  69.         System.out.println(job.getKey().getName() + " 将在: " + dateFormat.format(ft)  
  70.                 + "  时运行.并且重复: " + trigger.getRepeatCount() + " 次, 每次间隔 "  
  71.                 + trigger.getRepeatInterval() / 1000 + " 秒");  
  72.   
  73.         System.out.println("------- 开始调度 (调用.start()方法) ----------------");  
  74.         sched.start();  
  75.   
  76.         // 给任务留时间运行  
  77.         Thread.sleep(600L * 1000L);  
  78.   
  79.         sched.shutdown(true);  
  80.         System.out.println("------- 调度已关闭 ---------------------");  
  81.   
  82.         // 显示一下 已经执行的任务信息  
  83.         SchedulerMetaData metaData = sched.getMetaData();  
  84.         System.out.println("~~~~~~~~~~  执行了 "  
  85.                 + metaData.getNumberOfJobsExecuted() + " 个 jobs.");  
  86.     }  
  87. }  


-------------------------------------我是分割线----------------------------------------------------------

先说明 一个词 : misfire -- 指的是 错过了触发时间

你会注意到2个触发器具有相同的时间安排,相同的任务

触发器设定每3秒钟触发一次,但是工作需要10秒钟的执行时间

因此,在一次任务结束执行前,触发器已经错失触发(除非错失触发时限被设置为超过7)

 

其中第二个任务设置自己的错失触发指示:.withMisfireHandlingInstructionNowWithRemainingCount()

  所以当检测到丢失触发时,不会立即触发,而是忽略本次安排到下一个预定时间去触发

 

我们的结论 :

a)       本范例中,触发的间隔被设置为3秒,但是由于任务体执行间睡眠10秒,导致了错失触发的产生。

b)       实际运行的效果,2个任务执行的间隔为10秒。

c)        由于丢失触发时,job2的策略是立即触发,而job1是等待下一次机会触发。所以job2会赶在job1的前头,最终运行次数大于job1

 

Quartz 的 Misfire处理规则:

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


CronTrigger

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

withMisfireHandlingInstructionIgnoreMisfires
——以错过的第一个频率时间立刻开始执行
——重做错过的所有频率周期后
——当下一次触发频率发生时间大于当前时间后,再按照正常的Cron频率依次执行

withMisfireHandlingInstructionFireAndProceed
——以当前时间为触发频率立刻触发一次执行
——然后按照Cron频率依次执行


SimpleTrigger

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

withMisfireHandlingInstructionIgnoreMisfires

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

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


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

 

另外,如果任务数超过了Quartz的线程池中的线程数时,也会发生类似的情况 兄弟们可以参考一下下面的这篇博文:

http://www.cnblogs.com/makemelaugh/archive/2012/06/17/2533105.html