quartz - misfire错过触发时机的处理

简介: 1. 引言 要弄清楚作业的misfire,首先需要了解几个重要的概念: 触发器超时 : 举个例子说明这个概念。比如调度引擎中有5个线程,然后在某天的下午2点 有6个任务需要执行,那么由于调度引擎中只有5个线程,所以在2点的时候会有5个任务会按照之前设定的时间正常执行,有1个任务因为没有线程资源而被延迟执行,这个就叫触发器超时。

1. 引言

要弄清楚作业的misfire,首先需要了解几个重要的概念:

触发器超时 :

举个例子说明这个概念。比如调度引擎中有5个线程,然后在某天的下午2点 有6个任务需要执行,那么由于调度引擎中只有5个线程,所以在2点的时候会有5个任务会按照之前设定的时间正常执行,有1个任务因为没有线程资源而被延迟执行,这个就叫触发器超时。下面这些情况会造成触发器超时:

1)系统因为某些原因被重启。在系统关闭到重新启动之间的一段时间里,可能有些任务会 被 misfire;

2)Trigger 被暂停(suspend)的一段时间里,有些任务可能会被 misfire;

3)线程池中所有线程都被占用,导致任务无法被触发执行,造成 misfire;

4)有状态任务在下次触发时间到达时,上次执行还没有结束;

misfireThreshold :

misfireThreshold 即触发器超时的临界值,它可以在quartz.properties文件中配置。misfireThreshold是用来设置调度引擎对触发器超时的忍耐时间。假设misfireThreshold设置为6000(单位毫秒),那么它的意思说当一个触发器超时时间大于misfireThreshold时,调度器引擎就认为这个触发器真正的超时(即Misfires)。换言之,如果一个触发器超时时间小于设定的misfireThreshold, 那么调度引擎则不认为触发器超时。也就是说这个job并没发生misfire。

quartz.properties中的配置

#判定job为misfire的阈值,这里设置为4S
org.quartz.jobStore.misfireThreshold = 4000

那么,调度器对于触发器超时但是超时时间小于misfireThreshold 或者 触发器已经misfire 的两种情况是怎么处理的呢?

2. 调度器怎么处理超时

2.1 timeout < misfireThreshold

为了制造超时的现象,实验时把线程池的大小设定为1,misfireThreshold设定为5S。实验中定义了两个job,一个是busy job,它在运行期休眠了3S(

//BusyJob.java
public class BusyJob implements Job {
    private final Logger logger = LoggerFactory.getLogger(BusyJob.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String jobName = context.getJobDetail().getKey().getName();

        logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 开始执行");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 执行完毕");
    }

}
//TimeoutJob.java
public class TimeoutJob implements Job {
    private final Logger logger = LoggerFactory.getLogger(TimeoutJob.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        String jobName = context.getJobDetail().getKey().getName();

        logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 开始执行");

        logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 执行完毕");
    }

}
//TimeoutButNotMisfireTest.java
/**
 * 触发器超时,但没有misfire
 */
public class TimeoutButNotMisfireTest {

    public static void main(String[] args) throws SchedulerException, InterruptedException {

        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        // busy job
        JobDetail busyJob = JobBuilder //
                .newJob(BusyJob.class)//
                .withIdentity("busy job", "group1")//
                .build();

        SimpleTrigger busyTrigger = TriggerBuilder //
                .newTrigger() //
                .withIdentity("busy job trigger", "group1")//
                .startNow() //
                .withPriority(5) // 高优先级
                .withSchedule(SimpleScheduleBuilder.simpleSchedule() //
                        .withRepeatCount(0) //
                ).build();
        scheduler.scheduleJob(busyJob, busyTrigger);

        // timeout job
        JobDetail timeoutJob = JobBuilder //
                .newJob(TimeoutJob.class)//
                .withIdentity("timeout job", "group2")//
                .build();

        SimpleTrigger timeoutTrigger = TriggerBuilder //
                .newTrigger() //
                .withIdentity("timeout job trigger", "group2")//
                .startNow() //立即触发
                .withPriority(1) // 低优先级
                .withSchedule(SimpleScheduleBuilder.simpleSchedule() //
                        .withIntervalInSeconds(1) //每隔1S触发一次
                        .withRepeatCount(7) // 循环7次
                ).build();
        scheduler.scheduleJob(timeoutJob, timeoutTrigger);

        scheduler.start();

        Thread.sleep(20 * 1000);

        scheduler.shutdown(true);

    }
}

运行结果:

INFO  11:31:11,420 com.github.thinwonton.quartz.sample.misfire.BusyJob: [busy job] 在  : [ 11:31:11] 开始执行
 INFO  11:31:14,420 com.github.thinwonton.quartz.sample.misfire.BusyJob: [busy job] 在  : [ 11:31:14] 执行完毕
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 开始执行
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 执行完毕
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 开始执行
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 执行完毕
 INFO  11:31:14,423 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 开始执行
 INFO  11:31:14,423 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 执行完毕
 INFO  11:31:14,426 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 开始执行
 INFO  11:31:14,426 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 执行完毕
 INFO  11:31:15,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:15] 开始执行
 INFO  11:31:15,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:15] 执行完毕
 INFO  11:31:16,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:16] 开始执行
 INFO  11:31:16,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:16] 执行完毕
 INFO  11:31:17,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:17] 开始执行
 INFO  11:31:17,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:17] 执行完毕
 INFO  11:31:18,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:18] 开始执行
 INFO  11:31:18,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:18] 执行完毕

通过观察运行结果,我们可以得到结论:

超时的触发器(超时时间小于misfireThreshold)在获取到运行线程后,将会立即运行前面错过的作业job,然后按照前面制定的周期性任务正常运行。

2.2 timeout >= misfireThreshold

对于触发器超时,并且超时时间大于设定的misfireThreshold 这种情况,调度器引擎为简单触发器SimpleTrigger和表达式CronTrigger提供了多种处理策略,我们可以在定义触发器时指定需要的策略。

2.2.1 对于SimpleTrigger的处理策略

  • MISFIRE_INSTRUCTION_FIRE_NOW : 调度引擎在MisFire的情况下,将任务(JOB)马上执行一次。需要注意的是 这个指令通常被用做只执行一次的Triggers,也就是没有重复的情况(non-repeating),如果这个Triggers的被安排的执行次数大于0。那么这个执行与 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT 相同。
  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT: 调度引擎重新调度该任务,repeat count 保持不变,按照原有制定的执行方案执行repeat count次,但是,如果当前时间,已经晚于 end-time,那么这个触发器将不会再被触发。举个例子:比如一个触发器设置的时间是 10:00 执行时间间隔10秒 重复10次。那么当10:07秒的时候调度引擎可以执行这个触发器的任务,然后按照原有制定的时间间隔执行10次。但是如果触发器设置的执行时间是10:00,结束时间为10:10,由于种种原因导致该触发器在10:11分才能被调度引擎触发,这时,触发器将不会被触发了。
  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT: 这个策略跟上面的 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 策略类似,唯一的区别就是调度器触发触发器的时间不是“现在” 而是下一个 scheduled time。
  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT: 这个策略跟上面的策略 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 比较类似,调度引擎重新调度该任务,repeat count 是剩余应该执行的次数,也就是说本来这个任务应该执行10次,但是已经错过了3次,那么这个任务就还会执行7次。
  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT: 这个策略跟上面的 MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT 策略类似,区别就是repeat count 是剩余应该执行的次数而不是全部的执行次数。比如一个任务应该在2:00执行,repeat count=5,时间间隔5秒, 但是在2:07才获得执行的机会,那任务不会立即执行,而是按照机会在2点10秒执行。
  • MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY: 这个策略跟上面的 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 策略类似,但这个策略是忽略所有的超时状态,快速执行之前错过的次数,然后再按照之前制定的周期触发触发器。举个例子,一个SimpleTrigger 每个15秒钟触发, 但是超时了5分钟才获得执行的机会,那么这个触发器会被快速连续调用20次, 追上前面落下的执行次数。

2.2.2 对于CronTrigger的处理策略

  • MISFIRE_INSTRUCTION_FIRE_ONCE_NOW: 指示触发器超时后会被立即安排执行。
  • MISFIRE_INSTRUCTION_DO_NOTHING: 这个策略与策略 MISFIRE_INSTRUCTION_FIRE_ONCE_NOW 正好相反,它不会被立即触发,而是获取下一个被触发的时间,并且如果下一个被触发的时间超出了end-time 那么触发器就不会被执行。

文章转载自 开源中国社区 [http://www.oschina.net]

目录
相关文章
|
NoSQL Java Redis
Spring boot整合Redis实现发布订阅(超详细)
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收信息。微信,微博,关注系统 Redis客户端可以订阅任意数量的频道
7931 0
Spring boot整合Redis实现发布订阅(超详细)
|
Android开发
获取APP版本号:versionName、versionCode---Android基础篇
获取APP版本号:versionName、versionCode---Android基础篇
2486 0
|
域名解析 Kubernetes Java
图文详述Nacos配置中心使用:应用间配置共享、扩展配置文件加载优先级、新老版本差异
图文详述Nacos配置中心使用:应用间配置共享、扩展配置文件加载优先级、新老版本差异
6094 1
图文详述Nacos配置中心使用:应用间配置共享、扩展配置文件加载优先级、新老版本差异
|
存储 Java 调度
技术笔记:quartz(从原理到应用)详解篇(转)
技术笔记:quartz(从原理到应用)详解篇(转)
|
存储 Java 关系型数据库
MyBatisPlus中的TypeHandler | 自定义字段类型转换Handler
MyBatisPlus中的TypeHandler | 自定义字段类型转换Handler
2001 0
|
Linux Docker 容器
docker 国内镜像源
【8月更文挑战第26天】
3617 1
|
缓存 Java
java: 警告: 源发行版 17 需要目标发行版 17,java17 无效的目标发行
java: 警告: 源发行版 17 需要目标发行版 17,java17 无效的目标发行
9348 3
|
存储 安全 Java
settings.xml详解(很详细读这一篇就够了)
settings.xml是Java项目中用于配置Maven的重要文件,它详细规定了Maven的运行规则和行为。该文件通常位于用户家目录下的.m2文件夹中,或者项目根目录下的.mvn文件夹内。settings.xml中包含了众多配置项,从代理设置、镜像仓库配置,到服务器认证信息、插件组等,均可以在此文件中进行细致定义。通过合理配置settings.xml,我们可以优化Maven的依赖下载速度,保障仓库访问的安全性,甚至实现私有仓库的搭建与管理。深入了解settings.xml的每一项配置,对于提高Maven使用效率、保障项目构建稳定性具有重要意义。因此,无论是Maven初学者还是资深用户,都应仔
13472 3
|
SQL NoSQL 前端开发
MySQL 连接数过多的处理方法合集 - Too many connections - 卡拉云
碰到`Can not connect to MySQL server. Too many connections”-mysql`错误着实令人抓狂。这基本等于失去了对 MySQL 的控制权。本教程将详细讲解多种处理此错误的方法。 sudo mysql -uroot -p ERROR 1040 (00000): Too many connections
22790 1
|
存储 SQL 缓存
MySQL 配置文件 my.cnf / my.ini 逐行详解
充分理解 MySQL 配置文件中各个变量的意义对我们有针对性的优化 MySQL 数据库性能有非常大的意义。我们需要根据不同的数据量级,不同的生产环境情况对 MySQL 配置文件进行优化。Windows 和 Linux 下的 MySQL 配置文件的名字和存放位置都是不同的,WIndows 下 MySQL 配置文件是 `my.ini` 存放在 MySQL 安装目录的根目录下;Linux 下 MySQL 配置文件是 `my.cnf` 存放在 `/etc/my.cnf`、`/etc/mysql/my.cnf`。我们也可以通过 `find` 命令进行查找。
36472 2

热门文章

最新文章