文章目录
一、Quartz简介
深入解读Quartz任务调度器
参考URL: https://blog.youkuaiyun.com/DuShiWoDeCuo/article/details/78543110
Quartz大致可分为三个主要的核心:
1、调度器Scheduler:是一个计划调度器容器,容器里面可以盛放众多的JobDetail和Trigger,当容器启动后,里面的每个JobDetail都会根据Trigger按部就班自动去执行.
2、任务Job:要执行的具体内容。JobDetail:具体的可执行的调度程序,包含了这个任务调度的方案和策略。
3、触发器Trigger:调度参数的配置,什么时候去执行调度。
原理:
可以这么理解它的原理:调度器就相当于一个容器,装载着任务和触发器。任务和触发器又是绑定在一起的,然而一个任务可以对应多个触发器,但一个触发器却只能对应一个任务。
当JobDetail和Trigger在scheduler容器上注册后,形成了装配好的任务作业(JobDetail和Trigger所组成的一对儿),就可以伴随容器启动而调度执行了。
二、重要组成
Job
Job:表示一个工作,要执行的具体内容。此接口中只有一个方法。要创建一个任务,必须得实现这个接口。该接口只有一个execute方法,任务每次被调用的时候都会执行这个execute方法的逻辑,类似TimerTask的run方法,在里面编写业务逻辑。
生命周期:在每次调度器执行job时,它在调用execute方法前会创建一个新的job实例,当调用完成之后,关联的job对象实例会被释放,释放的实例会被垃圾回收机制回收。
- JobBuilder:可向任务传递数据,通常情况下,我们使用它就可向任务类发送数据了,如有特别复杂的传递参数,它提供了一个传递递:JobDataMap对象的方法
JobDetail jobDetail = JobBuilder.newJob(TestJob.class).withIdentity("testJob","group1").build();
- JobDetail:用来保存我们任务的详细信息。一个JobDetail可以有多个Trigger,但是一个Trigger只能对应一个JobDetail。
- JobStore:负责跟踪所有你给scheduler的“工作数据”:jobs, triggers, calendars, 等。
RAMJobStore:是使用最简单的也是最高效(依据CPU时间)的JobStore 。RAMJobStore 正如它名字描述的一样,它保存数据在RAM。缺点是你的应用结束之后所有的数据也丢失了–这意味着RAMJobStore 不具有保持job和trigger持久的能力。对于一些程序是可以接受的,甚至是期望的,但对于其他的程序可能是灾难性的。使用RAMJobStore配置Quartz:配置如下
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
JDBCJobStore: 以JDBC的方式保存数据在数据库中。它比RAMJobStore的配置复杂一点,也没有RAMJobStore快。几乎适用于所有的数据库,广泛用于 Oracle。PostgreSQL, MySQL, MS SQLServer, HSQLDB, 和DB2。使用JDBCJobStore之前你必须首先创建一系列Quartz要使用的表。你可以发现表创建语句在Quartz发布目录的 “docs/dbTables”下面。你需要确定你的应用要使用的事务类型。如果你不想绑定调度命令(例如增加和移除Trigger)到其他的事务,你可以使用JobStoreTX (最常用的选择)作为你的Jobstore。如果你需要Quartz和其他的事务(例如在J2EE应用服务器中)一起工作,你应该使用JobStoreCMT ,Quartz 将让应用服务器容器管理这个事务。使用JobStoreTx配置Quartz:
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate #配置表的前缀 org.quartz.jobStore.tablePrefix = QRTZ_ #使用JNDI数据源的时候,数据源的名字 org.quartz.jobStore.dataSource = myDS
TerracottaJobStore:提供了一个方法:在不使用数据库的情况下使它具有收缩性和强壮性。可以是集群的也可以是非集群的,在这两种情况下为你的job数据提供了一个存储机制用于应用程序重启之间持久,因为数据是存储在Terracotta服务器。它的性能比使用数据库访问JDBCJobStore好一点儿(大约是一个数量级),但是明显比RAMJobStore慢。使用TerracottaJobStore配置Quartz:
org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore
org.quartz.jobStore.tcConfigUrl = localhost:9510
JobDataMap:中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。
JobDetail jobDetail = JobBuilder.newJob(TestJob.class).withIdentity("testJob","group1").usingJobData("date1","存内容").build();
public class TestJob implements Job { /**把要执行的操作,写在execute方法中 */ @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { JobKey key = jobExecutionContext.getJobDetail().getKey(); JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap(); String date1 = jobDataMap.getString("date1"); } }
触发器:用来触发执行Job
触发器通用属性:
Jobkey:表示job实例的标识,触发器被触发时,该指定的job实例会被执行
StartTime:表示触发器的时间表首次被触发的时间,它的值类型为:java.util.Date
EndTime:指定触发器的不再被触发的时间,它的值类型为:java.util.Date
未正常触发的任务:misfire job
没有在正常触发时间点触发的任务。主要由一下几种情况导致:
触发时间在应用不可用的时间内,比如重启
上次的执行时间过长,超过了下次触发的时间
任务被暂停一段时间后,重新被调度的时间在下次触发时间之后
处理misfire job的策略,需要在创建trigger的时候配置,每种trigger对应的枚举值都不同,具体在接口里面有定义。CronTrigger有2种处理misfire的策略:
线程
负责任务调度的几个线程:
(1)任务执行线程:通常使用一个线程池(SimpleThreadPool)维护一组线程,负责实际每个job的执行。
(2)Scheduler调度线程QuartzSchedulerThread :轮询存储的所有 trigger,如果有需要触发的 trigger,即到达了下一次触发的时间,则从任务执行线程池获取一个空闲线程,执行与该 trigger 关联的任务。
(3)处理misfire job的线程MisfireHandler:轮训所有misfire的trigger,原理就是从数据库中查询所有下次触发时间小于当前时间的trigger,按照每个trigger设定的misfire策略处理这些trigger。
三、纯spring环境下-quartz基本使用
1. maven引入
<!--quartz-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
2. spring 配置bean SchedulerFactoryBean
使用SchedulerFactoryBean集成Quarz Job与Spring
参考URL: https://www.iteye.com/blog/angelbill3-2371787
Spring通过SchedulerFactoryBean实现调度任务的配置。
使用SchedulerFactoryBean集成Quarz Job与Spring
本次集成主要用到了Spring提供的
org.springframework.scheduling.quartz.SchedulerFactoryBean,该类使得Spring application context可以创建或管理Quartz的Scheduler,包括注册JobDetails、Calendars和Triggers等。
有了这个类,可以Retire掉org.quartz.ee.servlet.QuartzInitializerListener这个Listener。
注:这个类兼容Quartz 2.1.4及以上版本,Spring 4.1及以上版本。
spring xml配置:
<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="quartzProperties">
<props>
<prop key="org.quartz.threadPool.threadCount">50</prop>
</props>
</property>
</bean>
spring 注解配置(未验证测试):
import java.util.Properties;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;
@Component
public class ComponentFactory {
@Bean
public SchedulerFactoryBean getSchedulerFactoryBean(JobFactory myJobFactory) throws Exception {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
bean.setJobFactory(myJobFactory);
bean.setSchedulerName("myscheduler");
Properties quartzProperties = new Properties();
quartzProperties.load(this.getClass().getResourceAsStream("/quartz.properties"));
bean.setQuartzProperties(quartzProperties);
return bean;
}
}
3. 定义一个Job实现类实现Job接口
具体执行job,是执行这个类的execute方法。
public class QuartzJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
LoggerUtil.info("quartz job starts");
//TODO
LoggerUtil.info("quartz job end");
}
}
4. 定义一个Quartz定时任务管理类
接口如下:定义添加job、删除job、修改job、查询job等接口
/**
* Quartz定时任务管理接口
*/
public interface QuartzManagerService {
/**
* 添加定时任务
* @param jobName
* @param cronStr
*/
void addJob(String jobName, String cronStr);
/**
* 移除定时任务
*
* @param jobNames
*/
void removeJob(List<String> jobNames);
/**
* 修改quartzJob表达式
* @param name
* @param cronStr
* @return
* @throws SchedulerException
*/
boolean modifyQuartzJob(String name, String cronStr) throws SchedulerException;
/**
* 根据名字判断某个QuartzJob是否存在
*
* @param name
* @return
* @throws SchedulerException
*/
boolean isExistQuartzJob(String name) throws SchedulerException;
}
注入之前spring 配置的bean SchedulerFactoryBean,实现相关 功能。
里面有具体使用,可以直接参考使用。
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.support.CronSequenceGenerator;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.Date;
import java.util.List;
public class QuartzManagerServiceImpl implements QuartzManagerService{
@Autowired
private SchedulerFactoryBean schedulerFactoryBean;
@Override
public void addJob(String jobName, String cronStr) {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
//任务构建withIdentity:相当于给任务起了个名字
JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class)
.withIdentity(new JobKey(jobName)).build();
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronStr)
.withMisfireHandlingInstructionDoNothing();
//按新的cronExpression表达式构建一个新的trigger:
// withIdentity:相当于给trigger起了个名字
CronTrigger trigger = TriggerBuilder.newTrigger()
.withSchedule(scheduleBuilder)
.withIdentity(new TriggerKey(jobName)).build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
LoggerUtil.error("failed add job to scheduler: ", e);
}
}
@Override
public void removeJob(List<String> jobNames) {
if(CollectionUtils.isEmpty(jobNames)){
throw new ServiceException("jobnames empty");
}
Scheduler scheduler = schedulerFactoryBean.getScheduler();
for (String name : jobNames) {
try {
//停止触发器
scheduler.pauseTrigger(new TriggerKey(name));
//移除触发器
scheduler.unscheduleJob(new TriggerKey(name));
//删除任务
scheduler.deleteJob(new JobKey(name));
} catch (SchedulerException e) {
LoggerUtil.error("SchedulerException: ", e);
}
}
}
@Override
public boolean modifyQuartzJob(String name, String cronStr) throws SchedulerException {
LoggerUtil.info("modifyQuartzJob start !");
if (!CronSequenceGenerator.isValidExpression(cronStr)) {
LoggerUtil.info("modifyQuartzJob fail:{}", cronStr);
return false;
}
Date date = null;
TriggerKey triggerKey = new TriggerKey(name);
Scheduler scheduler = schedulerFactoryBean.getScheduler();
CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
String oldTime = cronTrigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(cronStr)) {
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronStr)
.withMisfireHandlingInstructionDoNothing();
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(name)
.withSchedule(cronScheduleBuilder).build();
date = scheduler.rescheduleJob(triggerKey, trigger);
}
LoggerUtil.info("modifyQuartzJob end !");
return date != null;
}
@Override
public boolean isExistQuartzJob(String jobName) throws SchedulerException {
LoggerUtil.info("isExistQuartzJob start !");
TriggerKey triggerKey = new TriggerKey(jobName);
Scheduler scheduler = schedulerFactoryBean.getScheduler();
CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (cronTrigger == null) {
return false;
}
LoggerUtil.info("isExistQuartzJob end !");
return true;
}
}
总结:在上面的demo中,我们看到添加一个job 实现思路大致为:
注入schedulerFactoryBean
调 schedulerFactoryBean.getScheduler(); 获取Scheduler实例
创建JobDetail、CronTrigger实例
通过Scheduler实例scheduler.scheduleJob(jobDetail, trigger);
spring和quartz 动态增加删除任务
扩展:spring3整合quartz2,实现动态添加、删除定时任务
参考URL: https://blog.youkuaiyun.com/qq_35153200/article/details/79485238
详解Spring整合Quartz实现动态定时任务
参考URL: https://www.jb51.net/article/107339.htm
思路> 把任务与cronExpression存放在数据库中,最大化减少xml配置,创建一个工厂类,在实际调用时把任务的相关信息通过参数方式传入,由该工厂类根据任务信息来具体执行需要的操作,从而方便我们的动态修改。
四、非spring环境下:quartz基本使用
Quartz的基本使用之入门(2.3.0版本)
参考URL: https://www.cnblogs.com/zhanghaoliang/p/7886110.html
-
StdSchedulerFactory创建schedule调度器实例
-
编写任务执行类implements Job复写execute
-
生成jobDetail和trigger实例作为参数传入schedule调度器
-
schedule调度器启动即可
1、创建调度工厂(); //工厂模式
2、根据工厂取得调度器实例(); //工厂模式
3、Builder模式构建子组件<Job,Trigger> // builder模式, 如JobBuilder、TriggerBuilder、DateBuilder
4、通过调度器组装子组件 调度器.组装<子组件1,子组件2…> //工厂模式
5、调度器.start(); //工厂模式
demo如下;
- 创建一个类 HelloJob.java,这个类是编写我们的具体要实现任务(打印Hello Quartz)
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class HelloJob implements Job{
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//打印当前的执行时间 例如 2017-11-23 00:00:00
Date date = new Date();
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("现在的时间是:"+ sf.format(date));
//具体的业务逻辑
System.out.println("Hello Quartz");
}
}
- 创建一个类HelloScheduler.java,这个是具体触发我们的任务
public class HelloScheduler {
public static void main(String[] args) throws SchedulerException {
//创建一个jobDetail的实例,将该实例与HelloJob Class绑定
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("myJob").build();
//创建一个Trigger触发器的实例,定义该job立即执行,并且每2秒执行一次,一直执行
SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("myTrigger").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()).build();
//创建schedule实例
StdSchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
scheduler.start();
scheduler.scheduleJob(jobDetail,trigger);
}
}
五、cron表达式
spring cron表达式及解析过程
参考URL: https://blog.youkuaiyun.com/ukulelepku/article/details/54310035
六、问题点整理
cron表达式后台格式校验问题
CronsequenceGenerator负责解析cron表达式并提供next接口。
Spring提供了CronSequenceGenerator类以便计算cron表达式的具体时间点的next方法(Spring3.2版本)和cron是否合法的 isValidExpression 方法(Spring4.3版本)。
使用CronsequenceGenerator的静态isValidExpression 方法即可。
计算cron表达式下次执行日期
spring其实已经提供了现成的类给大家使用,它就是CronSequenceGenerator:在spring-context包里
修改cron表达式内容立即生效思路
思路1: quartz调度器先删除这个任务再添加这个任务。
思路2: 代码解决 通过scheduler调度器对象的rescheduleJob方法
scheduler.rescheduleJob(triggerKey, trigger);
public boolean modifyQuartzJob(String name, String cronStr) throws SchedulerException {
Date date = null;
TriggerKey triggerKey = new TriggerKey(name);
Scheduler scheduler = schedulerFactoryBean.getScheduler();
CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
String oldTime = cronTrigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(cronStr)) {
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronStr);
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(name)
.withSchedule(cronScheduleBuilder).build();
date = scheduler.rescheduleJob(triggerKey, trigger);
}
return date != null;
}
常见job不执行问题分析
quartz里job不执行的解决方案
参考URL: https://blog.youkuaiyun.com/dotalee/article/details/70845254
- quartz中线程总数太少导致
生产环境中碰到会有job一直不执行的情况,后来分析是因为quartz中线程总数太少,而项目中所有的job都是并发执行的就会导致当到达时间节点时如果线程已经用完,则JOB不会执行等到下次job执行时间节点,如果项目中存在很多job执行过程耗时比时间周期还长就会导致这种情况发生。
解决办法:
1) 更具实际情况动态调整quartz线程池大小
<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="quartzProperties">
<props>
<prop key="org.quartz.threadPool.threadCount">50</prop>
</props>
</property>
</bean>
2) 把不用并发执行的job禁止并发执行
禁用并发执行的job在未执行完时到达下一个开始时间不会占用一个新的线程去执行,但需要更具业务情况来确定该job是否需要并发执行。
最终,1,2结合先把总线程数调大,再将一些定时同步数据的job都改为非并发执行,解决!
- 修改系统时间导致
经过测试,系统时间往后调,quartz到周期任务时间点能正常执行。
系统时间往前调,quartz到周期时间点不执行。
问题分析:quartz默认记录的是周期的下次具体执行时间,你系统时间往前调,它记录的下次执行时间还是未来,所以到了上个周期任务执行时间,它不会执行。
解决思路:
1) quartz 是否有相关配置或接口,让支持系统时间往前调,还能正常执行? 暂未找到。
2) 系统业务代码,在启动服务时,做一个独立demon线程,判断系统时间是否往前调了,如果发现系统时间是往前调了,则重载整个quartz。
//独立线程,判断系统时间是否发生修改
//发生系统时间往前调情况,强制刷新加载周期任务
Thread thread = new Thread(){
@Override
public void run(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
while(true){
//判断当前时间比上次时间少
Long currentTimeMillis = System.currentTimeMillis();
LoggerUtil.info("currentTimeMillis: " + currentTimeMillis);
if(currentTimeMillis < lastTimeMillis){
System.out.println(" The system time changes to the past !");
taskManagerService.refreshJobTrigger();
}
lastTimeMillis = currentTimeMillis;
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("InterruptedException");
}
}
}
};
thread.start();
Quartz集群
[推荐-有源码分析]Quartz集群原理分析
参考URL: https://blog.youkuaiyun.com/ym123456677/article/details/80506850
Quartz的集群模式指的是一个集群下多个节点管理同一批任务的调度,通过共享数据库的方式实现,**保证同一个任务到达触发时间的时候,只有一台机器去执行该任务。**每个节点部署一个单独的quartz实例,相互之间没有直接数据通信。
为什么使用Quartz集群
虽然单个Quartz实例能给予我们很好的任务job调度能力,但它不能满足典型的企业需求,如可伸缩性、高可靠性满足。假如你需要故障转移的能力并能运行日益增多的 Job,Quartz集群势必成为你应用的一部分了。使用 Quartz 的集群能力可以更好的支持你的业务需求,并且即使是其中一台机器在最糟的时间挂掉了也能确保所有的 Job 得到执行。
像之前的quartz和spring scheduler如果我们部署在单台机器上,虽然表面上我们解决了多台机器重复执行的问题,但是却有隐含的风险。单台机器如果宕机,那么定时任务功能将会导致停止,所以要部署到多台,保证系统的高可用性。并且随着我们要执行的定时任务越来越多,那么单台的压力会越大。
如果我们部署到多台机器上,那么随之而来的问题就是重复记录如何避免。那么就需要我们去了解分布式锁的实现方式。而quartz的集群就采用了mysql的行级锁互斥来防止重复记录的。
Quartz源码学习总结
quartz源码分析——执行引擎和线程模型
参考URL: https://www.cnblogs.com/liuroy/p/7517777.html
Quartz SimpleThreadPool的源码,一个简单的线程池的实现原理
参考URL: https://blog.youkuaiyun.com/u012881904/article/details/77507179
Quartz的线程池解析
参考URL: https://www.cnblogs.com/chanedi/p/4169510.html
SimpleThreadPool
Quartz SimpleThreadPool的源码,一个简单的线程池的实现原理
参考URL:https://blog.youkuaiyun.com/u012881904/article/details/77507179
quartz默认使用SimpleThreadPool 线程池,也可以自定义实现类,并为quartzProperties配置org.quartz.threadPool.class参数。
这个线程池非常简单,都没有BlockingQueue:
SimpleThreadPool中可用资源的表示
private List< WorkerThread> workers;//总的线程
private LinkedList< WorkerThread> availWorkers = new LinkedList< WorkerThread>();//可用的线程
private LinkedList< WorkerThread> busyWorkers = new LinkedList< WorkerThread>();//工作中的线程资源
一般的线程实现无疑就是继承Thread和实现Runable两种方法
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("thread run...");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
这里的线程执行完了之后就回收了线程的资源…和我们的线程池的资源是有区别的,线程池的资源是一直保持运行的状态,没有被回收,看看WorkerThread的实现:
循环的执行Run,让线程永不停息(这样才线程的资源不会被回收),通过状态量控制线程的终止
总结:
整个代码使用wait和notify维持线程间的同步执行问题
对于共享线程的原理时使用Loop+wait+notify,当有线程执行的时候,被唤醒执行任务。
自己实现一个简化的线程池,帮助理解
SimpleThreadPool实践
参考URL: https://www.cnblogs.com/maozhige/p/5084302.html
核心思想如下:
- 线程池实例为单例
- 线程池实例中保存着一个线程数组,用来分发任务
- 线程池中通过一个BlockingQueue实例,来实现FIFO的任务队列,这个实例同时被线程数组中的每一个线程拥有
- 线程通过while循环,不断从队列中取出任务执行(Runnable
未测试。
RAMJobStore
public class RAMJobStore implements JobStore
Quartz源码——Quartz调度器的Misfire处理规则
Quartz源码——Quartz调度器的Misfire处理规则(四)
参考URL: https://blog.youkuaiyun.com/u010648555/article/details/53672738
Quartz的misfire理解
参考URL: https://blog.youkuaiyun.com/furzoom/article/details/54906263
CronTrigger的misfire机制—-默认的 Trigger.MISFIRE_INSTRUCTION_SMART_POLICY !!!
misfire用于Trigger触发时,线程池中没有可用的线程或者调度器关闭了,此时这个Trigger变为misfire。当下次调度器启动或者有可以线程时,会检查处于misfire状态的Trigger。而misfire的状态值决定了调度器如何处理这个Trigger。
对于周期性的任务,如果有misfire的情况出现,则会自动更新CronTrigger的时间周期
默认情况下会在当前时间马上执行前一个被misfire的任务
而如果设置MISFIRE_INSTRUCTION_DO_NOTHING,则不对misfire的任务做特殊处理,只从当前时间之后的下一次正常调度时间开始执行
产生misfire的前提是:
到了该触发执行时上一个执行还未完成,且线程池中没有空闲线程可以使用(或有空闲线程可以使用但job设置为@DisallowConcurrentExecution)且过期时间已经超过misfireThreshold
满足以上条件就会触发quartz的misfire。
参考
Spring 整合 Quartz 分布式调度
参考URL: http://www.sohu.com/a/228004375_827544
SpringBoot集成Quartz动态定时任务
参考URL: https://blog.youkuaiyun.com/upxiaofeng/article/details/79415108