Quartz 定时任务使用 —— 错过的任务怎么办?(七)

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

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

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

代码参照Quartz官方提供的示例5

job类:StatefulDumbJob.java

package com.anson.examples.example5;

import org.quartz.*;

import java.text.SimpleDateFormat;
import java.util.Date;

@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class StatefulDumbJob implements Job {

    // 静态常量,作为任务在调用间,保持数据的键(key)

    public static final String NUM_EXECUTIONS = "NumExecutions";    // 保存的计数每次递增1
    public static final String EXECUTION_DELAY = "ExecutionDelay";  // 任务在执行时,中间睡眠的时间。本例中睡眠时间过长导致了错失触发

    public void execute(JobExecutionContext context) throws JobExecutionException {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String jobRunTime = dateFormat.format(new Date());

        System.err.println("---" + context.getJobDetail().getKey().getName() + " 在  : [" + jobRunTime + "] 执行了!!");

        // 任务执行计数 累加
        JobDataMap map = context.getJobDetail().getJobDataMap();
        int executeCount = 0;
        if (map.containsKey(NUM_EXECUTIONS)) {
            executeCount = map.getInt(NUM_EXECUTIONS);
        }
        executeCount++;
        map.put(NUM_EXECUTIONS, executeCount);

        // 睡眠时间: 由调度类重新设置值 ,本例为 睡眠10s
        long delay = 5000l;
        if (map.containsKey(EXECUTION_DELAY)) {
            delay = map.getLong(EXECUTION_DELAY);
        }

        try {
            Thread.sleep(delay);
        } catch (Exception ignore) {
        }

        // 睡眠醒来后,打印任务执行结束的信息
        System.err.println("  -" + context.getJobDetail().getKey().getName() + " 完成次数  : " + executeCount);

    }

}

调度类: MisfireExample.java

package com.anson.examples.example5;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

import static org.quartz.DateBuilder.nextGivenSecondDate;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

public class MisfireExample {

    public void run() throws Exception {
        // 任务执行的时间 格式化
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        SchedulerFactory sf = new StdSchedulerFactory();
        Scheduler sched = sf.getScheduler();
        System.out.println("--------------- 初始化 -------------------");

        // 下一个第15秒
        Date startTime = nextGivenSecondDate(null, 15);

        // statefulJob1 每3s运行一次,但它会延迟10s
        JobDetail job = newJob(StatefulDumbJob.class)
                .withIdentity("statefulJob1", "group1")
                .usingJobData(StatefulDumbJob.EXECUTION_DELAY, 10000L) // 设置参数:睡眠时间 10s
                .build();
        SimpleTrigger trigger = newTrigger()
                .withIdentity("trigger1", "group1")
                .startAt(startTime)
                .withSchedule(simpleSchedule().withIntervalInSeconds(3).repeatForever()).build();


        Date ft = sched.scheduleJob(job, trigger);
        System.out.println(job.getKey().getName() + " 将在: " + dateFormat.format(ft) + "  时运行.并且重复: " + trigger.getRepeatCount() + " 次, 每次间隔 " + trigger.getRepeatInterval() / 1000 + " 秒");

        // statefulJob2 将每3s运行一次 , 但它将延迟10s, 然后不断的迭代, 与statefulJob1不同的是设置了错失触发后的调整策略
        job = newJob(StatefulDumbJob.class)
                .withIdentity("statefulJob2", "group1")
                .usingJobData(StatefulDumbJob.EXECUTION_DELAY, 10000L)// 设置参数:睡眠时间 10s
                .build();
        trigger = newTrigger()
                .withIdentity("trigger2", "group1")
                .startAt(startTime)
                .withSchedule(
                        simpleSchedule().withIntervalInSeconds(3).repeatForever()
                            // 设置错失触发后的调度策略
                            .withMisfireHandlingInstructionNowWithRemainingCount()
                )
                .build();

        ft = sched.scheduleJob(job, trigger);
        System.out.println(job.getKey().getName() + " 将在: " + dateFormat.format(ft) + "  时运行.并且重复: " + trigger.getRepeatCount() + " 次, 每次间隔 " + trigger.getRepeatInterval() / 1000 + " 秒");

        System.out.println("------- 开始调度 (调用.start()方法) ----------------");
        sched.start();

        // 给任务留时间运行
        Thread.sleep(60L * 1000L);

        sched.shutdown(true);
        System.out.println("------- 调度已关闭 ---------------------");

        // 显示一下 已经执行的任务信息
        SchedulerMetaData metaData = sched.getMetaData();
        System.out.println("~~~~~~~~~~  执行了 " + metaData.getNumberOfJobsExecuted() + " 个 jobs.");
    }

    public static void main(String[] args) throws Exception {
        MisfireExample example = new MisfireExample();
        example.run();
    }

}

执行结果如下

--------------- 初始化 -------------------
statefulJob1 将在: 2017-09-11 15:41:15  时运行.并且重复: -1 次, 每次间隔 3 秒
statefulJob2 将在: 2017-09-11 15:41:15  时运行.并且重复: -1 次, 每次间隔 3 秒
------- 开始调度 (调用.start()方法) ----------------
[INFO] 11 九月 03:41:02.523 下午 main [org.quartz.core.QuartzScheduler]
Scheduler MyScheduler_$_NON_CLUSTERED started.
---statefulJob2 在  : [2017-09-11 15:41:15] 执行了!!
---statefulJob1 在  : [2017-09-11 15:41:15] 执行了!!
  -statefulJob2 完成次数  : 1
  -statefulJob1 完成次数  : 1
---statefulJob2 在  : [2017-09-11 15:41:25] 执行了!!
---statefulJob1 在  : [2017-09-11 15:41:27] 执行了!!
  -statefulJob2 完成次数  : 2
---statefulJob2 在  : [2017-09-11 15:41:35] 执行了!!
  -statefulJob1 完成次数  : 2
---statefulJob1 在  : [2017-09-11 15:41:39] 执行了!!
  -statefulJob2 完成次数  : 3
---statefulJob2 在  : [2017-09-11 15:41:45] 执行了!!
  -statefulJob1 完成次数  : 3
---statefulJob1 在  : [2017-09-11 15:41:51] 执行了!!
  -statefulJob2 完成次数  : 4
---statefulJob2 在  : [2017-09-11 15:41:55] 执行了!!
  -statefulJob1 完成次数  : 4
---statefulJob1 在  : [2017-09-11 15:42:03] 执行了!!
  -statefulJob2 完成次数  : 5
---statefulJob2 在  : [2017-09-11 15:42:05] 执行了!!
  -statefulJob1 完成次数  : 5
---statefulJob1 在  : [2017-09-11 15:42:15] 执行了!!
  -statefulJob2 完成次数  : 6
---statefulJob2 在  : [2017-09-11 15:42:15] 执行了!!
  -statefulJob1 完成次数  : 6
  -statefulJob2 完成次数  : 7
---statefulJob2 在  : [2017-09-11 15:42:25] 执行了!!
---statefulJob1 在  : [2017-09-11 15:42:27] 执行了!!
  -statefulJob2 完成次数  : 8
---statefulJob2 在  : [2017-09-11 15:42:35] 执行了!!
  -statefulJob1 完成次数  : 7
---statefulJob1 在  : [2017-09-11 15:42:39] 执行了!!
  -statefulJob2 完成次数  : 9
---statefulJob2 在  : [2017-09-11 15:42:45] 执行了!!
  -statefulJob1 完成次数  : 8
---statefulJob1 在  : [2017-09-11 15:42:51] 执行了!!
...
... 省略内容
...
---statefulJob1 在  : [2017-09-11 15:49:03] 执行了!!
  -statefulJob2 完成次数  : 47
---statefulJob2 在  : [2017-09-11 15:49:05] 执行了!!
  -statefulJob1 完成次数  : 40
---statefulJob1 在  : [2017-09-11 15:49:15] 执行了!!
  -statefulJob2 完成次数  : 48
---statefulJob2 在  : [2017-09-11 15:49:15] 执行了!!
  -statefulJob1 完成次数  : 41
  -statefulJob2 完成次数  : 49
---statefulJob2 在  : [2017-09-11 15:49:25] 执行了!!

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

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

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

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

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

结论 :

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

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

3、由于丢失触发时,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_IGNORE_MISFIRE_POLICY

这个不是忽略已经错失的触发的意思,而是说忽略MisFire策略。它会在资源合适的时候,重新触发所有的MisFire任务,并且不会影响现有的调度时间。

比如,SimpleTrigger每15秒执行一次,而中间有5分钟时间它都MisFire了,一共错失了20个,5分钟后,假设资源充足了,并且任务允许并发,它会被一次性触发。

这个属性是所有Trigger都适用。

MISFIRE_INSTRUCTION_FIRE_NOW

忽略已经MisFire的任务,并且立即执行调度。这通常只适用于只执行一次的任务。

MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT

将startTime设置当前时间,立即重新调度任务,包括的MisFire的

MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT

类似MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT,区别在于会忽略已经MisFire的任务

MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT

在下一次调度时间点,重新开始调度任务,包括的MisFire的

MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT

类似于MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT,区别在于会忽略已经MisFire的任务。

MISFIRE_INSTRUCTION_SMART_POLICY

所有的Trigger的MisFire默认值都是这个,大致意思是“把处理逻辑交给聪明的Quartz去决定”。基本策略是,

如果是只执行一次的调度,使用MISFIRE_INSTRUCTION_FIRE_NOW

如果是无限次的调度(repeatCount是无限的),使用MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT

否则,使用MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT



赞(52) 打赏
未经允许不得转载:优客志 » JAVA开发
分享到:

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏