目录
Trigger 概述与常用触发器
Trigger 常用属性
Trigger 优先级 priority
过期触发策略(misfire Instructions)
org.quartz.Calendar 排除日历
SimpleTrigger 触发器
CronTrigger 触发器
Quartz 数据持久化
1、org.quartz.Trigger 是基接口,具有所有触发器通用的属性,使用 org.quartz.TriggerBuilder 类实例化实际触发器。
2、触发器有一个关联的 TriggerKey,它应该在单个 Scheduler 中唯一标识它们。
3、多个触发器可以指向同一个作业(Job),但是一个触发器只能指向一个作业。
4、触发器可以通过 JobDataMap 放置属性将参数/数据传输到 Job 。《quartz-scheduler 核心 API》
//设置触发器的启动时间为 30 秒后,然后每10秒执行一次任务.
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10)
.repeatForever())
.startAt(DateBuilder.futureDate(30, DateBuilder.IntervalUnit.SECOND))
.build();
SimpleTrigger | 简单触发器,在一个给定的时间时刻,并有选择地以指定的间隔重复。使用 SimpleScheduleBuilder 构建 |
DailyTimeIntervalTrigger | 每日间隔时间触发器,根据每天的重复时间间隔触发。使用 DailyTimeIntervalScheduleBuilder 构建。 |
CalendarIntervalTrigger | 日历触发器,以每 N 个日历时间单位触发一次。使用 CalendarIntervalScheduleBuilder 构建. |
CronTrigger | cron 表达式触发器,灵活指定 年月日时分秒星期。使用 CronScheduleBuilder 构建。 |
TriggerKey | 标识 trigger 的身份。键由名称和组构成,且名称在组中必须唯一。TriggerKey(String name, String group),name 不能为 null,否则抛异常,group 为 null 时,使用默认值 “DEFAULT”。 |
startTime | 1)设置触发器开始的时刻,属性值是 java.util.Date 类型,默认值为 startTime = new Date(); 2)有些类型的 trigger,会在设置的 startTime 时刻立即触发,有些类型的 trigger,则会在 startTime 之后才开始生效。 3)如当前系统时间为1月3号,设置一个 trigger 在每个月的第5天执行任务,startTime 属性设置为 3 月 2 号,则该 trigger 第一次触发是在3月5号,然后是 4月5号,以此类推。 |
endTime | 设置 trigger 失效的时刻。比如 ”每月第5天执行” 的 trigger,如果其 endTime 是 6 月30 号,则其最后一次执行时间是 6 月 5 号。 |
Trigger 的属性在构建 trigger 的时候可以通过 TriggerBuilder 设置,比如:
//设置触发器的启动时间为 startTime,失效时间为 endTime。
//一到启动时间 SimpleTrigger 就会第一次执行任务,然后间隔 withIntervalInSeconds 继续执行
//直到失效时间不再执行,触发器也会被卸载.
Trigger trigger = TriggerBuilder.newTrigger()
.startAt(startTime)
.endAt(endTime)
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10)
.repeatForever())
.build();
1、如果 trigger 很多,或者 Quartz 线程池的工作线程太少,Quartz 可能没有足够的资源同时触发所有的 trigger,这种情况下可能希望控制哪些 trigger 优先使用 Quartz 的工作线程。
2、Trigger 的 priority 属性用于设置优先级,比如有 30 个 trigger 需要同时触发,但只有 15 个工作线程,此时优先级最高的 15 个 trigger 会被优先触发。如 TriggerBuilder.newTrigger().withPriority(2).xxx;
3、如果没有为 trigger 设置优先级(priority ),则默认为 5;priority 属性值可以是任意整数,正数、负数都可以。数字越小优先级越高
4、只有同时触发的 trigger 之间才会比较优先级,10:59 触发的 trigger 总是在 11:00 触发的 trigger 之前执行。
5、如果 trigger 是可恢复的,在恢复后再调度时,优先级与原 trigger 是一样的。
6、验证非常简单,减小 quartz 线程池中的线程数(quartz.properties Quartz Configuration),然后同时执行多个触发器,观察设置 priority 属性的区别即可 :
#quartz 线程池中的线程个数,如 10 个线程表示最多可以同时执行10个任务/作业
org.quartz.threadPool.threadCount: 10
源码:https://github.com/wangmaoxiong/quartzapp/blob/master/src/test/java/com/wmx/quartzapp/helloworld/HaiJobTest.java
1、trigger 的属性 misfireInstruction:表示如果 scheduler 关闭了,或者 Quartz 线程池中因为当时没有可用的线程来执行 job 而错过触发时间,此时持久性的 trigger 就会错过(miss)其触发时间,即错过触发(misfire)。
2、不同类型的 trigger,有不同的 misfire 机制,它们默认都使用“错过触发智能策略(misfire_instruction_smart_policy)”,根据 trigger 的类型和配置动态调整行为。
3、当 scheduler 启动的时候,查询所有错过触发(misfire) 的持久性 trigger,然后根据它们各自的 misfire 机制更新 trigger 的信息。
4、过期策略都定义为了常量,汇总如下:
Trigger 接口公共过期策略 | |
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1 | 忽略策略 |
MISFIRE_INSTRUCTION_SMART_POLICY = 0 | 智能策略 |
SimpleTrigger 触发器过期策略 | |
MISFIRE_INSTRUCTION_FIRE_NOW = 1 | 立即执行策略 |
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT = 2 | 对现有的重新开始计数执行 |
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT = 3 | 重新安排,重复计数 |
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT = 4 | 重新安排下一个,剩余计数 |
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT = 5 | 重新安排下一步,现有计数 |
CronTrigger 触发器过期策略 | |
MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1 | 现在执行一次 |
MISFIRE_INSTRUCTION_DO_NOTHING = 2 | 什么都不做 |
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1 | 忽略策略 |
5、misfire 策略作为基本调度(simple schedule)的一部分进行配置(通过 XxxSchedulerBuilder 设置),如:
//创建简单触发器,每 30 秒触发一次,使用 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 过期策略
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.startNow()
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(30)
.repeatForever()
.withMisfireHandlingInstructionNowWithExistingCount())
.build();
本节源码:SimpleTriggerTest.java
//每月1号晚上 23:30 执行一次。
//monthlyOnDayAndHourAndMinute(int dayOfMonth, int hour, int minute) :表示每月 dayOfMonth 号 hour 点 minute 分 0 秒执行一次.
//等价于 cron 表达式:String cronExpression = String.format("0 %d %d %d * ?", minute, hour,dayOfMonth);
//使用过期策略:CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(CronScheduleBuilder.monthlyOnDayAndHourAndMinute(6, 21, 44)
.withMisfireHandlingInstructionFireAndProceed())
.build();
1、org.quartz.Calendar 用于从 trigger 的调度计划中排除时间段,比如可以创建一个 trigger,每个工作日的上午 9:30 执行,然后增加一个 Calendar,排除掉所有的商业节日。
2、可以在定义和存储 trigger 的时候与 org.quartz.Calendar 对象进行关联。任何实现了Calendar接口的可序列化对象都可以作为Calendar对象。
3、Calendar 排除时间段的单位可以精确到毫秒,使用时必须先实例化,然后通过 addCalendar() 方法注册到 scheduler。同一个Calendar 实例可用于多个 trigger。
AnnualCalendar | 年日历,如可用于排除每年同一日期的银行假日 |
CronCalendar | cron 日历,用于排除 cron 表达式指定的时间。 |
DailyCalendar | 每日日历,此日历不包括每天指定的时间范围。例如可以使用此日历不包括每天的营业时间(上午8点至下午5点) 每个 DailyCalendar 只允许指定一个时间范围,且该时间范围不能跨越每日界限(即不能指定从晚上8点到早上5点) |
HolidayCalendar | 假日日历,用于按天排除,如排除每个周末. |
MonthlyCalendar | 月日历,用于排除一个月中的某些天 |
WeeklyCalendar | 周日历,用于排除一周中的某些天 |
3、下面以 HolidayCalendar 进行演示:
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.calendar.HolidayCalendar;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class HaiJob2Test {
public static void main(String[] args) {
try {
/**1)创建调度器*/
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
/**2)创建任务详情*/
JobDetail jobDetail = JobBuilder.newJob(HaiJob.class)
.withIdentity("haiJob2", "haiJobGroup")
.build();
/**3)创建假日日历 HolidayCalendar,精确到天,表示触发器遇到这一天时,不触发执行任务(放假).
* HolidayCalendar extends BaseCalendar implements org.quartz.Calendar:假日日历,全天被排除在日程之外,
* 即调度器遇到这些日期就放假,不执行任务.
* addExcludedDate(Date excludedDate):将给定日期(年月日)添加到排除日期列表中,即使设置了时分秒,也会被强制置为 0,存放在 TreeSet 中.
* removeExcludedDate(Date dateToRemove):移除指定假期日历,会从 TreeSet 中进行移除.
*/
Date excludedDate1 = new SimpleDateFormat("yyyy-MM-dd").parse("2020-04-04");
Date excludedDate2 = new SimpleDateFormat("yyyy-MM-dd").parse("2020-04-05");
HolidayCalendar holidayCalendar = new HolidayCalendar();
holidayCalendar.removeExcludedDate(null);
holidayCalendar.addExcludedDate(excludedDate1);
holidayCalendar.addExcludedDate(excludedDate2);
/**向调度程序添加(注册)给定的 "日历"
* scheduler.addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers)
* calName:假期日历的名称,触发器会根据名称进行引用、calendar:假期日历
* replace:表示调度器 scheduler 中如果已经存在同名的日历是否替换
* updateTriggers:是否更新引用了现有日历的现有触发器,以使其基于新触发器是"正确的"
* scheduler.getCalendar(String calName):根据名称获取调度器中注册好的日历
* scheduler.getCalendarNames():获取调度器中注册的所有日历的名称.
*/
scheduler.addCalendar("hc1", holidayCalendar, true, true);
/**
* 4)创建触发器
* withIdentity(TriggerKey triggerKey):设置触发器的名称以及所属组名称,同一组内的触发器名称必须唯一
* dailyAtHourAndMinute(int hour, int minute):cron 触发器,在每天的 hour 时 minute 分 0 秒触发.
* modifiedByCalendar(String calName):按日历修改触发器,对假期日历 hc1 不触发执行任务。
*/
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(CronScheduleBuilder.dailyAtHourAndMinute(17, 18))
.modifiedByCalendar("hc1")
.build();
//注册任务详情与触发器,然后启动调度器
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
}
}
源码:https://github.com/wangmaoxiong/quartzapp/tree/master/src/test/java/com/wmx/quartzapp/helloworld
1、SimpleTrigger 通常用于在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。
2、SimpleTrigger 的属性包括:开始时间、结束时间、重复次数、重复的时间间隔。
2.1)开始时间(startTime):设置触发器开始的时刻,属性值是 java.util.Date 类型,默认值为 startTime = new Date();
2.2)结束时间(endTime):结束时间属性值会覆盖重复次数属性值。如设置 trigger 在终止时间之前每隔10秒执行一次,则不需要计算开始时间和终止时间之间的重复次数,只需设置终止时间并将重复次数设为 SimpleTrigger.repeat_indefinitely=-1(当然也可以将重复次数设置为一个教大的值,并保证该值比trigger在终止时间之前实际触发的次数要大即可)。
2.3)重复次数(repeatCount):默认是 0、如果是常量 SimpleTrigger.REPEAT_INDEFINITELY = -1,则表示无限期循环.
2.4)重复间隔(interval):默认是 0,单位为毫秒。注意如果重复间隔为 0,trigger 将会以重复次数并发执行(或者以scheduler可以处理的近似并发数)。
3、SimpleTrigger 实例通过 TriggerBuilder 设置公共属性,通过 SimpleScheduleBuilder 设置与 SimpleTrigger 相关的属性。
4、使用 org.quartz.DateBuilder 可以非常方便地构造基于开始时间(或终止时间)的调度策略。
一:指定开始触发时间,不重复,到点就会触发一次:
/**
* startAt:触发器开始时间,不设置时,默认 startTime = new Date(),则立即会执行.
* dateOf(int hour, int minute, int second) 返回 Date 对象,表示今天(new Date())的 hour时minute分second秒开始.
* withIdentity(TriggerKey triggerKey):设置触发器的名称以及所属组名称,同一组内的触发器名称必须唯一
*/
Trigger trigger = TriggerBuilder.newTrigger()
.startAt(DateBuilder.dateOf(15, 30, 0))
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.build();
1)上面如果没写 startAt,则一启动就会执行,因为启动时间默认为 new Date()
2)dateOf(int hour, int minute, int second) 从源码可知,它先是 new Date(),然后通过 java.util.Calendar 的 set 方法将时分秒设置为 hour、minute、second,最后返回 date.
二:从指定时间开始触发,然后每隔 5 秒执行一次,重复 10 次:
/**
* startAt:触发器开始时间,不设置时,默认 startTime = new Date(),则立即会执行.
* dateOf(int hour, int minute, int second,int dayOfMonth, int month)
* 表示 month 月 dayOfMonth 日 hour 点 minute 分 second 秒开始触发
* withIdentity(TriggerKey triggerKey):设置触发器的名称以及所属组名称,同一组内的触发器名称必须唯一
*/
Trigger trigger = TriggerBuilder.newTrigger()
.startAt(DateBuilder.dateOf(15, 42, 0, 6, 4))
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5)
.withRepeatCount(10))
.build();
在 startAt 时刻会执行第一次,然后重复执行了 10 次,所以实际上面总共是执行了 11 次任务.
三:5 分钟以后开始触发,每隔 10 分钟执行一次,直到今天晚上 22:00:
/**
* startAt:触发器开始时间,不设置时,默认 startTime = new Date(),则立即会执行.
* futureDate(int interval, IntervalUnit unit):表示未来时间,多少时间后,如 5 分钟后,1小时后,1天后,3个月后等
* withIdentity(TriggerKey triggerKey):设置触发器的名称以及所属组名称,同一组内的触发器名称必须唯一
* repeatForever():方法内部是将循环次数 repeatCount=-1; 即无限循环,直到 endTime 失效时间
*/
Trigger trigger = TriggerBuilder.newTrigger()
.startAt(DateBuilder.futureDate(5, DateBuilder.IntervalUnit.MINUTE))
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMinutes(10)
.repeatForever())
.endAt(DateBuilder.dateOf(22, 0, 0))
.build();
1)IntervalUnit 是枚举,有:MILLISECOND, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR.
2)futureDate(int interval, IntervalUnit unit) 方法内部也是通过 java.util.Calendar 的 add 方法将指定间隔单位(unit) 加上 interval 值,最后返回 date.
四:当前时间下一个小时的整点触发,然后每 2 小时重复一次:
/**
* startAt:触发器开始时间,不设置时,默认 startTime = new Date(),则立即会执行.
* evenHourDateAfterNow:表示当前时间后面最近的整点,如当前时间为 16:20,则返回 17:00
* evenSecondDate(Date date):也可以返回时间后面的整点,date 为 null,默认为 new Date();
* withIdentity(TriggerKey triggerKey):设置触发器的名称以及所属组名称,同一组内的触发器名称必须唯一
* repeatForever():方法内部是将循环次数 repeatCount=-1; 即无限循环.直到 endTime 失效时间
*/
Trigger trigger = TriggerBuilder.newTrigger()
.startAt(DateBuilder.evenHourDateAfterNow())
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInHours(2)
.repeatForever())
.build();
官网源码:DateBuilder.java ,本节示例源码:SimpleTriggerTest.java
1、CronTrigger 触发器使用起来更加灵活,基本可以代替 SimpleTrigger,使用 CronScheduleBuilder 构建。
2、Cron 表达式是一个字符串,用空格隔开,分为6或7个域,从左到右为:秒 分 时 月份中的日期 月 星期中的日期 年份。年份可写可不写。
3、网上介绍 cron 表达式的文章有很多,使用 在线Cron表达式生成器 可以轻松生成 cron 表达式,下面简单举几例:
3.1)"0/3 * * * * ?" : 每 3 秒钟执行一次
3.2)"40 0/30 * * * ?":每 30 分钟在第 40 秒时刻执行一次
3.3)"0 0/30 1 * * * ?":在每天凌晨 1 点内,每 30 分钟在第 0 秒时刻执行一次
3.4)"0 30 10-13 ?* WED,FRI":每周三、周五的10:30、11:30、12:30、13:30 执行一次
3.5)"0 0/30 8-9 5,20 * ?":每月5日、20日8至10点之间每半小时触发一次,8:00,8:30,9:00,9:30(注意10点不触发)3.6)星期可以指定为1到7(1是星期日),或者字符串 SUN,MON,TUE,WED,THU,FRI,SAT
3.7)月份可以指定为0到11,或者为 JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC。
3.8)"?" 字符用于日期和星期字段,当其中一个有值时,另一个就设置为 "?"
3.9)"*" 表示任意时刻
4、对于类似“每天上午9:00至10:00之间每5分钟,下午1:00至晚上10点之间每20分钟一次”的需求,通常直接创建两个触发器运行相同的作业来解决。
5、startTime 、endTime 属于公共属性,所有触发器都可以进行设置。
一:每天上午 8 点至下午 5 点之间,每隔一分钟触发一次
//每天上午 8 点至下午 5 点之间,每隔一分钟触发一次,默认启动时不会执行,启动1分钟后开始执行第一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(CronScheduleBuilder.cronSchedule("0 0/1 8-17 * * ?"))
.build();
二:每周一、三上午 9:30 执行一次:
//每周一、三上午9:30执行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(CronScheduleBuilder.cronSchedule("0 30 9 ? * MON,WED"))
.build();
三:每月初一晚上 23:30 执行一次:
//每月1号晚上 23:30 执行一次。
//monthlyOnDayAndHourAndMinute(int dayOfMonth, int hour, int minute) :表示每月 dayOfMonth 号 hour 点 minute 分 0 秒执行一次.
//等价于 cron 表达式:String cronExpression = String.format("0 %d %d %d * ?", minute, hour,dayOfMonth);
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(CronScheduleBuilder.monthlyOnDayAndHourAndMinute(1, 23, 30))
.build();
monthlyOnDayAndHourAndMinute(int dayOfMonth, int hour, int minute) | 每月几号几点几分执行一次,等价: String.format("0 %d %d %d * ?", minute, hour,dayOfMonth); |
dailyAtHourAndMinute(int hour, int minute) | 每天几点几分执行一次,等价: String.format("0 %d %d ? * *", minute, hour) |
weeklyOnDayAndHourAndMinute(int dayOfWeek, int hour, int minute) | 每周几点几分执行一次,等价: String.format("0 %d %d ? * %d", minute, hour,dayOfWeek); |
CronTrigger 过期触发策略(misfire Instructions)
本节源码:CronTriggerTest.java
1、org.quartz.spi.JobStore 负责跟踪调度程序的所有"工作数据",如 jobs,triggers,Calendar 等。
RAMJobStore | 内存存储。性能最高,程序关闭后,所有调度数据会丢失。切换 RAMJobStore,只需如下设置即可(这也是默认方式): org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore #指定存储方式为 RAM 内存存储 |
JobStoreTX | 数据库存储。如果不需要将调度命令(例如添加和删除triggers)绑定到其他事务,那么可以通过使用 JobStoreTX 管理事务(这是最常见的选择)。org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX |
JobStoreCMT | 数据库存储。如果需要 Quartz 与其他事务(即J2EE应用程序服务器)一起工作,那么应该使用 JobStoreCMT,这种情况下,Quartz 将让应用程序服务器容器管理事务。org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreCMT |
2、quartz 默认就是使用的内存存储,也就是说当什么都没有配置的时候,它就是 RAMJobStore。《quartz-scheduler 定时器概述、核心 API 与 快速入门》中也是内存存储。
3、《Spring Boot 2.1.3 集成 Quartz 定时器, jdbc 持久化调度信息》中使用 JobStoreTX 将数据持久化到数据库。
quartz.properties Quartz Configuration
4、实际生产中通常都是使用 JDBC 持久化,配置的调度信息都存储在数据中,应用服务器重启之后自动读取调度信息,然后继续按着规则进行自动执行,就像是一块钟表即使没电了,下次上了电池之后,仍然会自动运行。需要使用 jdbc 存储到数据库中,则可以参考《Spring Boot 2.1.3 集成 Quartz 定时器, jdbc 持久化调度信息》