新手向笔记,来源多为网上博客例子。
SpringBoot整合Quartz简单完整例子
SpringBoot整合Quartz作为调度中心完整实用例子
spring-boot-2.0.3之quartz集成,不是你想的那样哦!
精通SpringBoot——第十篇:使用Quartz实现动态配置定时任务
quartz初次使用踩坑记
Quartz任务调度(1)概念例析快速入门
注意:cron方式需要用到的4张数据表:
qrtz_job_details;qrtz_triggers;qrtz_cron_triggers;qrtz_fired_triggers
QRTZ_JOB_DETAILS
存储每一个已配置的任务Job的详细信息
QRTZ_TRIGGERS
存储已配置的触发器Trigger的信息
QRTZ_CRON_TRIGGERS
存储Cron Trigger,包括Cron表达式和时区信息
QRTZ_FIRED_TRIGGERS
存储每个正在执行的触发器Trigger相关的状态信息,以及相联Job的执行信息
QRTZ_SCHEDULER_STATE
记录 调度器(每个机器节点)的生命状态
QRTZ_LOCKS
记录程序的悲观锁(防止多个节点同时执行同一个定时任务)
QRTZ_SIMPLE_TRIGGERS
存储已配置的Simple Trigger的信息
QRTZ_SIMPROP_TRIGGERS
存储CalendarIntervalTrigger和DailyTimeIntervalTrigger两种类型的触发器
QRTZ_BLOB_TRIGGERS
Trigger作为Blob类型存储(用于Quartz用户用JDBC创建他们自己定制的Trigger类型,JobStore并不知道如何存储实例的时候)
QRTZ_CALENDARS
以Blob类型存储Quartz的Calendar日历信息,quartz可配置一个日历来指定一个时间范围
QRTZ_PAUSED_TRIGGER_GRPS
存储已暂停的Trigger组的信息
知乎:最牛逼的任务调度工具 | Quartz
描述任务要执行的具体内容,任务要做什么,比如打印输出文字等等
注:Job类必须单独一个文件,必须为public
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
* 任务执行的内容
* 注:必须单独一个文件
* 注:必须为public
**/
public class MyJob1 implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("...abc打印123456...");
}
}
任务的信息,如名称、组别、描述等
// 2. 创建 JobDetail
JobDetail jobDetail =JobBuilder.newJob(MyJob1.class)
.withIdentity("job1", "group1")
.withDescription("MyJob1Detail des.")
.build();
触发器的信息,如名称、组别、描述等
触发器开始执行的时间、周期、间隔等;
用 TriggerBuilder.newTrigger() 建立;接着定义各种属性;
普通的触发器
// 3. 创建触发器Trigger
Trigger trigger =
TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group")
.withDescription("trigger1 des.")
// .startNow()
.startAt(DateBuilder.dateOf(10, 00, 00))
.endAt(DateBuildr.dateOf(11,00,00))
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
// 间隔
.withIntervalInSeconds(1)
// 重复次数,7次
// .repeatForever()
.withRepeatCount(7)
// 若失效,之后再恢复并马上执行
.withMisfireHandlingInstructionFireNow()
)
.forJob("job1", "group1")
.build();
使用基于日历的触发机制的触发器,cron表达式
// 使用基于日历的触发机制的触发器 CronTrigger
String cron1 = "* * * * * ? *";
CronTrigger cronTrigger = (CronTrigger)TriggerBuilder.newTrigger()
.withIdentity("cron trigger 1", "group")
.withDescription("cron trigger 1 des.")
.startNow()
.withSchedule(CronScheduleBuilder
.cronSchedule(cron1)
)
.build();
调度器的创建模式:一般用 工厂模式 SchedulerFactory 创建
调度器所要调度的 任务的信息(jobDetail)、所要调度的 触发器(trigger)
开始执行任务: scheduler.start();
// 4. 创建调度器 Scheduler
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
Quartz框架(三)—任务的并行/串行执行
使用注解方式:注解在 org.quartz 包的内部,注解在Job类上边
@DisallowConcurrentExecution:禁止任务并发执行
@PersistJobDataAfterExecution:正常执行完任务后,JobDataMap 中的数据应该被改动,以被下一次调用时使用
import lombok.extern.log4j.Log4j2;
import org.quartz.*;
/**
* Job 是 定时任务的具体执行逻辑
* JobDetail 是 定时任务的定义
* 注解 DisallowConcurrentExecution:禁止任务并发执行
* 注解 PersistJobDataAfterExecution:正常执行完任务后,JobDataMap 中的数据应该被改动,以被下一次调用时用
* @author EalenXie
* @date 2019/7/10
*/
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
@Log4j2
public class SayHelloJobLogic implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 任务具体逻辑
log.info("任务(共5秒)开始 SayHelloJob.execute , start... ");
// 延时5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("任务(共5秒)结束 SayHelloJob.execute , end... ");
}
}
较新版本的quartz,StatefulJob 这个类已经过期,所以有状态的Job类,指的是没有并行执行那两个注解(@DisallowConcurrentExecution、@PersistJobDataAfterExecution)的Job类、
Quartz框架(四)—misfire处理机制
任务框架quartz的misfire的理解
在Quartz中,当一个持久化的触发器因为:
- 调度器被关闭; scheduler shuwdown
- 线程池没有可用线程; pool no free threads
- 项目重启; project restart
- 任务的串行(并发)执行; jobs excute concurrently / serially
而错过激活时间,就会发生激活失败(misfire)。 miss trigger time. / misfire
// 所有的misfile任务马上执行
public static final int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1;
// 在Trigger中默认选择 MISFIRE_INSTRUCTION_FIRE_ONCE_NOW 策略
public static final int MISFIRE_INSTRUCTION_SMART_POLICY = 0;
// CornTrigger默认策略,合并部分misfire,正常执行下一个周期的任务。
// 比如在恢复后,共有两个misFire,则合并为一个misFire,于是只执行这一个misFire
public static final int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1;
// 所有的misFire都不管,执行下一个周期的任务。
public static final int MISFIRE_INSTRUCTION_DO_NOTHING = 2;
对应 CronTrigger 类的内部源代码:
public CronScheduleBuilder withMisfireHandlingInstructionIgnoreMisfires() {
this.misfireInstruction = -1;
return this;
}
public CronScheduleBuilder withMisfireHandlingInstructionFireAndProceed() {
this.misfireInstruction = 1;
return this;
}
public CronScheduleBuilder withMisfireHandlingInstructionDoNothing() {
this.misfireInstruction = 2;
return this;
}
TriggerBuilder.newTrigger()
// trigger 名称、组名
.withIdentity(jobKey.getName(), jobKey.getGroup())
// trigger 描述
.withDescription(description)
// 使用 Cron 和对应的调度器 CronScheduleBuilder
.withSchedule(CronScheduleBuilder
.cronSchedule(cronExpression)
// 选择 misFire 策略
// 1.所有的 misFire 都不管,执行下一个周期的任务。
.withMisfireHandlingInstructionDoNothing()
// 不选择任何策略的话,则默认为该策略
// 2.CronTrigger 默认策略,合并部分misfire,正常执行下一个周期的任务
// .withMisfireHandlingInstructionFireAndProceed()
// 3. 所有的misfire任务马上执行
// .withMisfireHandlingInstructionIgnoreMisfires()
)
.usingJobData(jobDataMap)
.build();
Quartz框架(六)— Trigger状态转换
正常获取、触发任务执行的流程:
一个触发器只能绑定一个Job,但是一个Job可以有多个触发器
调度器线程执行的时候,首先从triggers表中获取状态为WAITING,并且将要触发的Trigger。然后将WAITING状态更新为ACQUIRED,表示该触发器抢占到了,防止其他调度器(实例)抢占。然后插入触发器信息以及实例名到FRIED_TRIGGERS表中,状态为ACQUIRED。前面的更新和后面的插入是在一个事务中进行的。
该触发器抢占到任务后,等待触发时间的到来。
执行时间到来后,每触发一次任务,都会在 FIRED_TRIGGERS 表中创建一条记录,并且状态为 EXECUTING
如果任务允许并发执行,此时TRIGGERS表里的状态更新为 WAITING,PAUSED,COMPLETE(不需要执行)。
如果任务不允许并发执行,还会把Triggers表里的状态更新为BLOCK或PAUSED_BLOCK。
注意:Triggers表更新时根据 任务名 和 任务所属组名 来更新,而不是 触发器名称 和 触发器组名 来更新的。
这就解决了一个任务有多个触发器的并发问题;然后触发器线程会创建一个执行环境来执行任务,以便在任务执行完成后更新触发器的状态。任务执行完成后,在一个事务中触发器状态更新为WAITING,删除FIRED_TRIGGERS表里对应的记录。
如何避免多个节点执行同一个任务:
qrtz_trigger 表中有 NEXT_FIRE_TIME 字段(下一次触发时间)。每个任务在即将执行的时候,获取qrtz_locks 表中的行级锁,开启一个事务(更新 qrtz_trigger 表状态为ACQUIRED,并且插入 qrtz_fire_trigger 一条数据,起始状态为 ACQUIRED)。
Quartz框架(八)— Quartz实现异步通知
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.example.execute.SayHelloJobLogic;
import lombok.extern.log4j.Log4j2;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import static org.quartz.TriggerBuilder.newTrigger;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Calendar;
/**
* 测试类
*@author 小胖
*@date 2019/07
**/
@Log4j2
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestQuartzForMoreJob {
/**
* 一个任务Job 绑定多个触发器
* @author 小胖
* @date 2019/07
*/
@Test
public void MultiTriggersTest() {
// 设置执行次数:1次
SimpleScheduleBuilder simpleScheduleBuilder =
SimpleScheduleBuilder.repeatSecondlyForTotalCount(1);
// 定义任务信息:前面列出的占用时间5秒的打印文字Job
JobDetail jobDetail = JobBuilder.newJob(SayHelloJobLogic.class)
.withIdentity("job1", "job_group1")
.storeDurably()
.build();
// 定义时间格式变量
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
// 当前时间
Date date = new Date();
// 时间操作,当前时间加 5秒
Date date1 = addSecond(date, 5);
// 时间操作,当前时间加 15秒
Date date2 = addSecond(date, 15);
// 日志打印
// 当前时间, 如 2020-02-20 09:00:00
log.info("获取到任务的时间:" + simpleDateFormat.format(date));
// 当前时间 + 5s, 如 2020-02-20 09:00:05
log.info("第一次通知的时间:" + simpleDateFormat.format(date1));
// 当前时间 + 15s,如 2020-02-20 09:00:15
log.info("第二次通知的时间:" + simpleDateFormat.format(date2));
// 触发器1:当前时间 + 5s
SimpleTrigger trigger1 = newTrigger().withIdentity("trigger1", "group1")
.withSchedule(simpleScheduleBuilder)
.startAt(date1)
.forJob(new JobKey("job1", "job_group1"))
.withDescription("trigger1, + 5s ")
.build();
// 触发器2:当前时间 + 15s
SimpleTrigger trigger2 = newTrigger().withIdentity("trigger2", "group1")
.withSchedule(simpleScheduleBuilder)
.startAt(date2)
.forJob(new JobKey("job1", "job_group1"))
.withDescription("trigger2, + 15s ")
.build();
try {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 多触发器关联
scheduler.scheduleJob(jobDetail, trigger1);
scheduler.scheduleJob(trigger2);
scheduler.start();
// 任务完成,再过100s,关闭调度器
Thread.sleep(100000);
scheduler.shutdown();
} catch (SchedulerException | InterruptedException e) {
e.printStackTrace();
}
}
/**
* 时间操作,对一个具体的时间增加多少秒
* @param date 时间
* @param second 增加的秒数
* @return java.util.Date
* @author 小胖
* @date 2019/07
*/
public static Date addSecond(Date date, int second) {
java.util.Calendar calendar = java.util.Calendar.getInstance();
calendar.setTime(date);
// calendar.add(Calendar.MINUTE, minute);
calendar.add(Calendar.SECOND, second);
return calendar.getTime();
}
}
SpringBoot整合Quartz简单完整例子
/**
* Quartz 里面 一个最简单,最基本的定时任务 应该包含以下必要基本属性
* 注意 : 这里只是方便演示,故写此类进行简单说明, 可针对自身业务对此类进行扩展
* @author EalenXie
* @date 2019/07
*/
@Data
@Builder
public class JobInfo {
/**
* 定时任务 的名字和分组名
*/
@NotNull(message = "定时任务的 名字 和 组名 坚决不为空")
private JobKey jobKey;
/**
* 定时任务 的描述(可以定时任务本身的描述,也可以是触发器的)
*/
private String description;
/**
* 定时任务 的执行cron
*/
@NotEmpty(message = "定时任务的执行cron 不能为空")
private String cronExpression;
/**
* 定时任务 的元数据
*/
private Map<?, ?> jobDataMap;
/**
* 定时任务 的 具体执行逻辑类
*/
@NotNull(message = "定时任务的具体执行逻辑类 坚决不能为空")
private Class<? extends Job> jobClass;
}
/**
* 创建和启动 定时任务
* @param jobInfo 定时任务
*/
public void startJob(JobInfo jobInfo) throws SchedulerException {
// 1.定时任务 的 名字和组名
JobKey jobKey = jobInfo.getJobKey();
// 2.定时任务 的 元数据
JobDataMap jobDataMap = getJobDataMap(jobInfo.getJobDataMap());
// 3.定时任务 的 描述
String description = jobInfo.getDescription();
// 4.定时任务 的 逻辑实现类
Class<? extends Job> jobClass = jobInfo.getJobClass();
// 创建 JobDetail 、Trigger
JobDetail jobDetail = getJobDetail(jobKey, description, jobDataMap, jobClass);
Trigger trigger = getTrigger(jobKey, description, jobDataMap, cron);
// 启动调度器
scheduler.scheduleJob(jobDetail, trigger);
}
/**
* 获取定时任务的定义
* JobDetail是任务的定义, Job是任务的执行逻辑
* @param jobKey 定时任务的名称 组名
* @param description 定时任务的 描述
* @param jobDataMap 定时任务的 元数据
* @param jobClass 定时任务的 真正执行逻辑定义类
* @return org.quartz.JobDetail
*/
public JobDetail getJobDetail(JobKey jobKey,
String description,
JobDataMap jobDataMap,
Class<? extends Job> jobClass) {
return JobBuilder.newJob(jobClass)
.withIdentity(jobKey)
.withDescription(description)
.setJobData(jobDataMap)
.usingJobData(jobDataMap)
.requestRecovery()
.storeDurably()
.build();
}
/**
* 获取Trigger (Job的触发器,执行规则)
* @param jobKey 定时任务的名称 组名
* @param description 定时任务的 描述
* @param jobDataMap 定时任务的 元数据
* @param cronExpression 定时任务的 执行cron表达式
* @return org.quartz.Trigger
*/
public Trigger getTrigger(JobKey jobKey,
String description,
JobDataMap jobDataMap,
String cronExpression) {
return TriggerBuilder.newTrigger()
// trigger 名称、组名
.withIdentity(jobKey.getName(), jobKey.getGroup())
// trigger 描述
.withDescription(description)
//
.withSchedule(CronScheduleBuilder
.cronSchedule(cronExpression)
// 选择 misFire 策略:
// 1. 所有的 misFire 都不管,执行下一个周期的任务。
.withMisfireHandlingInstructionDoNothing()
// 2.CronTrigger 默认策略,合并部分misfire,正常执行下一个周期的任务
// .withMisfireHandlingInstructionFireAndProceed()
// 3. 所有的misfile任务马上执行
// .withMisfireHandlingInstructionIgnoreMisfires()
)
.usingJobData(jobDataMap)
.build();
}
/**
* 获取任务的元数据
* @param map 不限量的(序列化的)数据对象
* @return org.quartz.JobDataMap
*/
public JobDataMap getJobDataMap(Map<?, ?> map) {
return map == null ? new JobDataMap() : new JobDataMap(map);
}
用于当任务调度中你所关注事件发生时,能够及时获取这一事件的通知。类似于任务执行过程中的邮件、短信类的提醒;
对Job(任务)建立一个监听器,分别对任务执行 《之前, 之后, 取消》 3个阶段进行监听;
实现监听器需要实现JobListener接口,然后注册到Scheduler上;
Quartz框架(十)监听
Quartz使用(4) - Quartz监听器Listerner
【Quartz】 JobListener、Triggerlistener、SchedulerListener介绍与使用
任务调度过程中,与任务Job相关的事件包括:Job开始要执行的提示;;Job执行完成的提示灯
Quartz任务调度(4) JobListener分版本超详细解析
// (1) Job Listener
// 监听器 Job Listener
MyJobListener myJobListener = new MyJobListener();
// 指定任务的 Job Listener
KeyMatcher<JobKey> jobMatcher = KeyMatcher.keyEquals(jobDetail.getKey());
// 指定任务的 Job Listener
KeyMatcher<JobKey> cronJobMatcher = KeyMatcher.keyEquals(nonStateJobDetail.getKey());
// 指定组的 Job Listener
GroupMatcher<JobKey> groupJobMatcher = GroupMatcher.jobGroupEquals("group2");
// 创建并注册,局部的 Job Listener
// scheduler.getListenerManager().addJobListener(myJobListener, jobMatcher);
// scheduler.getListenerManager().addJobListener(myJobListener, cronJobMatcher);
// 创建并注册,全局的 Job Listener
scheduler.getListenerManager().addJobListener(myJobListener, EverythingMatcher.allJobs());
任务调度过程中,与触发器Trigger相关的事件包括:触发器触发、触发器未正常触发、触发器完成等。
Quartz任务调度(5) TriggerListener分版本超详细解析
// (2) Trigger Listener
MyTriggerListener myTriggerListener = new MyTriggerListener("MyTriggerListener");
// 获取 triggerKey
TriggerKey triggerKey = trigger.getKey();
TriggerKey cronTriggerKey = cronTrigger.getKey();
// 指定任务的 Trigger Listener
KeyMatcher<TriggerKey> triggerMatcher = KeyMatcher.keyEquals(triggerKey);
KeyMatcher<TriggerKey> cronTriggerMatcher = KeyMatcher.keyEquals(cronTriggerKey);
// 一个特定组的 Trigger Listener
// GroupMatcher groupTriggerMatcher = GroupMatcher.groupEquals("group1");
// GroupMatcher groupTriggerMatcher1 = GroupMatcher.groupStartsWith("g");
// GroupMatcher groupTriggerMatcher2 = GroupMatcher.groupEndsWith("2");
// 创建并注册,局部的 Trigger Listener
// scheduler.getListenerManager().addTriggerListener(myTriggerListener,triggerMatcher);
// scheduler.getListenerManager().addTriggerListener(myTriggerListener, cronTriggerMatcher);
// 全局的 Trigger Listener
scheduler.getListenerManager().addTriggerListener(myTriggerListener, EverythingMatcher.allTriggers());
SchedulerListener会在Scheduler的生命周期中关键事件发生时被调用。
与Scheduler有关的事件包括:增加一个Job/Trigger,删除一个Job/Trigger,Scheduler发生严重错误,关闭Scheduler等。
Quartz任务调度(6) SchedulerListener分版本超详细解析
// (3) Scheduler Listener
MySchedulerListener mySchedulerListener = new MySchedulerListener();
scheduler.getListenerManager().addSchedulerListener(mySchedulerListener);
// 5. 启动任务
// 启动调度器
log.info("开启调度器 scheduler start...");
scheduler.start();
// 20秒
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 暂停Job
scheduler.pauseJob(jobDetail.getKey());
scheduler.pauseJob(nonStateJobDetail.getKey());
// 绑定新的 CronTrigger
String newCron = "0/15 * * * * ? ";
CronTrigger cronTrigger1 = TriggerBuilder.newTrigger()
.withIdentity("newCronTrigger", "group2")
.withDescription("a new cron")
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(newCron))
.forJob("nonStatedJob", "group2")
.build();
TriggerKey triggerKey1 = cronTrigger1.getKey();
scheduler.rescheduleJob(triggerKey1, cronTrigger1);
// 恢复运行Job
scheduler.resumeJob(jobDetail.getKey());
// 20秒
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 删除Job
scheduler.deleteJob(jobDetail.getKey());
// 卸载触发器
scheduler.unscheduleJob(triggerKey);
scheduler.unscheduleJob(cronTriggerKey);
// 6. 停止任务
// 停止调度器
log.info("停止调度器 scheduler shutdown...");
scheduler.shutdown();
java quartz框架创建定时任务异常: Trigger does not reference given job!
Quartz调度器-触发器不引用给定的作业:Trigger does not reference given job
JobDetail jobDetail =
JobBuilder.newJob(MyJob1.class)
.withIdentity("job1", "group1")
.withDescription("jobDetail des.")
.build();
Trigger trigger =
TriggerBuilder.newTrigger()
.withIdentity("SimpleTrigger1", "SimpleGroup")
.withDescription("SimpleTrigger1 des.")
.startNow()
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
// 间隔
.withIntervalInSeconds(2)
// 重复次数
.repeatForever()
// 若失效,之后再恢复并马上执行
.withMisfireHandlingInstructionFireNow()
)
// 检查此处是否与将要绑定的JobDetail中的Job名称和组名称是否相同
.forJob("job1", "group1")
.build();
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
// 监听器
MyJobListener myJobListener = new MyJobListener();
KeyMatcher<JobKey> keyMatcher = KeyMatcher.keyEquals(jobDetail.getKey());
scheduler.getListenerManager().addJobListener(myJobListener, keyMatcher);
// 若报错(Trigger does not reference given job)一般会指向这里
scheduler.scheduleJob(jobDetail, trigger);