Quartz 相关整理
文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
1. 概述
- Quartz 是 OpenSymphony 开源组织在 Job scheduling 领域又一个开源项目。
- 是一个完全由 Java 编写的开源 作业调度 框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。
Quartz 特点
- 纯 Java 实现,可以作为独立的应用程序,也可以嵌入在另一个独立式应用程序运行。
- 强大的调度功能,Spring 默认的调度框架,灵活可配置。
- 作业持久化,调度环境持久化机制,可以保存并恢复调度现场。
- 系统关闭数据不会丢失。
- 灵活的应用方式,可以任意定义触发器的调度时间表,支持任务和调度各种组合,组件式监听器、各种插件、线程池等功能,多种存储方式等。
- 分布式和集群能力,可以被实例化,一个 Quartz 集群中的每个节点作为一个独立的 Quartz 使用,通过相同的数据库表来感知到另一个 Quartz 应用。
Quartz 的三个核心概念
- 任务 Job 具体要执行的业务逻辑。
- 触发器 Trigger 定义触发业务的时间规则。
- 调度器 Scheduler 调度器去根据触发条件去调度任务执行。
引入 Quartz 的依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
编写一个 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 {
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");
}
}
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);
}
}
Quartz 中的设计模式
- 创建调度工厂()(工厂模式)。
- 根据工厂取得调度器实例()(工厂模式)。
- 构建子组件 <Job,Trigger>(如 JobBuilder、TriggerBuilder、DateBuilder)(Builder 模式)。
- 通过调度器组装子组件,调度器.组装<子组件1,子组件2...>(工厂模式)。
- 调度器.start()(工厂模式)
2. 核心类和关系
2.1 任务
Job 作业
- 由需要执行作业的类实现的接口,实现业务逻辑任务的接口。
- 只有一个
execute()
方法。 - JobExecutionContext 储存 Quartz 提供调度上下文信息以及 Job 本身的数据。
- 只有一个
public interface Job {
public void execute(JobExecutionContext context) throws JobExecutionException;
}
- Job 实例可以分为有状态和无状态。
- 无状态 Job 只在注册进 Scheduler 时才有 JobDataMap 数据,无状态 Job 运行结束后不保存 JobDataMap 数据,这样意味着在无状态 Job 运行过程中修改过的 JobDataMap 数据在无状态 Job 再次运行时将无法得到。
- 有状态 Job 与之相反,有状态 Job 每次运行结束时将存储有状态 Job 的 JobDataMap 数据,但是有状态 Job 为了保证 JobDataMap 的数据的一致性,无法并行运行,当 Trigger 需要再次触发执行一个正在运行的 Job 时,Trigger 会自动的延迟触发,直到正在运行的 Job 运行结束后才再次触发该 Job。
- 需要有状态 Job 时必须实现 StatefulJob 接口。
- 定义一个任务的过程。
- 创建一个 org.quartz.Job 的实现类,并实现实现自己的业务逻辑。
- 定义一个 JobDetail,引用这个实现类。
- 加入 scheduleJob。
- Quartz 调度一次任务的流程。
- JobClass jobClass=JobDetail.getJobClass()。
- Job jobInstance=jobClass.newInstance()。Job 实现类必须有一个 public 的无参构建方法。
- jobInstance.execute(JobExecutionContext context)。JobExecutionContext 是 Job 运行的上下文,可以获得 Trigger、Scheduler、JobDetail 的信息。
JobDetail 作业详情
- 传递给定作业实例的详细信息属性,用于定义作业的实例。
JobDetail jobDetail = new JobDetail("myJob", // job name
sched.DEFAULT_GROUP, // job group
TestJob.class); // job
属性 | 说明 |
---|---|
JobKey | 标识 Job 实例,包含 name 和 group 属性,name 为任务名称。group 为任务所在组,不设定默认为 default。 |
jobClass | Job 接口的实现类。 |
jobDataMap | Job 需要的参数数据。 |
description | Job 描述。 |
durability | 是否持久化。如果 Job 设置为非持久,当没有活跃的 trigger 与之关联的时候,Job 会自动从 scheduler 中删除。非持久 Job 的生命期是由 trigger 的存在与否决定。JobBuilder 中的属性。 |
shouldRecover | 是否可恢复。如果 Job 设置为可恢复,一旦 Job 执行时 scheduler 发生 hard shutdown(比如进程崩溃或关机),当 scheduler 重启后,该 Job 会被重新执行。JobBuilder 中的属性。可以通过 JobExecutionContext.isRecovering() 查询任务是否是被恢复的。 |
- 注册进 Quartz Scheduler 中的 Job 和 Trigger 是通过 name 来标识的,为了后期的维护,Job 和 Trigger 能够按类划分为 group,在一个 group 中每个 Job 和 Trigger 的 name 必须为唯一的,即 Job 和 Trigger 的标识是由各自的 name+group 组成的。
JobBuilder
- 用于定义/构建 JobDetail 实例,用于定义作业的实例。
JobDateMap
- 进行任务调度的时候 JobDateMap 存储在 JobExecutionContext 中。
- JobDateMap 用来装载任何可序列化的数据对象,Job 实例对象被执行时,这些参数对象会传递给它。
- JobDateMap 实现 Map 接口,支持键值对添加/存取基本类型数据类型的方法。
- 对于同一个 JobDetail 实例执行的多个 Job 实例,共享同样的 JobDataMap。
- 除了 JobDetail,Trigger 同样有一个 JobDataMap,共享范围是所有使用这个 Trigger 的 Job 实例。
Job 并发
- Job 是有可能并发执行的,比如一个任务要执行 10 秒中,而调度算法是每秒中触发 1 次,那么就有可能多个任务被并发执行。如果并不想任务并发执行,那么 @DisallowConcurrentExecution 注解可以解决。
public class DoNothingJob implements Job {
@DisallowConcurrentExecution
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("do nothing");
}
}
- @DisallowConcurrentExecution 是对 JobDetail 实例生效,如果定义两个 JobDetail,引用同一个 Job 类,是可以并发执行的。
JobExecutionException
- Job.execute() 方法是不允许抛出除 JobExecutionException 之外的所有异常的(包括RuntimeException),所以编码的时候,最好是 try-catch 住所有的 Throwable,小心处理。
2.2 触发器
Trigger
- 具有所有触发器通用属性的基本接口,描述了 Job 执行的时间触发规则。
- Quartz 中多种类型的 Trigger。
- SimpleTrigger,MutableTrigger,CronTirgger,CalendarIntervalTrigger,CoreTrigger 和 DailyTimeIntervalTrigger。
- 常用的有。
- SimpleTrigger 简单触发器。
- CalendarIntervalTrigger 日历触发器。
- CronTrigger Cron 表达式触发器。
- DailyTimeIntervalTrigger 日期触发器。
通用属性 | 说明 |
---|---|
JobKey | 标识 Job 实例,触发器根据此,指定 Job 实例执行。包含 name 和 group 属性,name 为任务名称。group 为任务所在组,不设定默认为 default。 |
StartTime | 触发器的时间表首次被触发的时间,值为 java.util.Date 。 |
EndTime | 触发器不再被触发的时间,值同上。 |
description | trigger 描述。 |
calendarName | 日历名称。 |
mayFireAgain | 是否重复执行。 |
priority | 优先级,默认为 5。当多个 trigger 同时触发 Job 时,线程池可能不够用,此时根据优先级来决定谁先触发。Priority 只会在 同时 触发 Trigger 的时候进行比较。如果 Trigger 上的 Job 需要恢复,那么恢复后会使用原始 Trigger 相同的 Priority。 |
misfireInstruction | 错过 Job(未在指定时间执行的 Job)的处理策略,默认为 MISFIRE_INSTRUCTION_SMART_POLICY。 |
TriggerState 触发器状态
状态 | 说明 |
---|---|
NONE | 无 |
NORMAL | 正常状态 |
PAUSED | 暂停状态 |
COMPLETE | 完成 |
ERROR | 错误 |
BLOCKED | 堵塞 |
CompletedExecutionInstruction 执行完成时状态
状态 | 说明 |
---|---|
NOOP | 无 |
RE_EXECUTE_JOB | 重复执行 |
SET_TRIGGER_COMPLETE | 触发器执行完成 |
DELETE_TRIGGER | 删除触发器 |
SET_ALL_JOB_TRIGGERS_COMPLETE | 所有作业和触发器执行完成 |
SET_TRIGGER_ERROR | 触发器执行错误 |
SET_ALL_JOB_TRIGGERS_ERROR | 设置所有都是错误的 |
TriggerBuilder
- 用于定义/构建触发器实例。
Misfire(错失触发)策略
- Scheduler 资源不足,或者机器崩溃重启等,可能某一些 Trigger 在应该触发的时间点没有被触发,这就是 MisFire。Trigger 需要一个策略来处理这种情况,每种 Trigger 可选的策略各不相同。
- SimpleTrigger 的 MisFire 策略。
-
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。
-
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY。
Calendar
- 作用在于补充 Trigger 的时间,可以排除或加入某一些特定的时间点。
- Quartz 提供以下几种 Calendar,所有的 Calendar 既可以是排除,也可以是包含。
- HolidayCalendar 指定特定的日期,比如 20190613,精度到天。
- DailyCalendar 指定每天的时间段(rangeStartingTime, rangeEndingTime),格式是 HH:MM[:SS[:mmm]],最大精度可以到毫秒。
- WeeklyCalendar 指定每星期的星期几,可选值比如为 java.util.Calendar.SUNDAY,精度是天。
- MonthlyCalendar 指定每月的几号。可选值为 1-31,精度是天。
- AnnualCalendar 指定每年的哪一天,使用方式如上例,精度是天。
- CronCalendar 指定 Cron 表达式。精度取决于 Cron 表达式,最大精度可以到秒。
SimpleTrigger
- 指定从某一个时间开始,以一定的时间间隔(单位是毫秒)执行的任务,适合的任务类似于:9:00 开始,每隔 1 小时,执行一次。
- repeatInterval 重复间隔。
- repeatCount 重复次数。实际执行次数是 repeatCount+1。因为在 startTime 的时候一定会执行一次。
simpleSchedule()
.withIntervalInHours(1) //每小时执行一次
.repeatForever() //次数不限
.build();
simpleSchedule()
.withIntervalInMinutes(1) //每分钟执行一次
.withRepeatCount(10) //次数为10次
.build();
CalendarIntervalTrigger
- 类似于 SimpleTrigger,指定从某一个时间开始,以一定的时间间隔执行的任务。
- 不同的是 SimpleTrigger 指定的时间间隔为毫秒,没办法指定每隔一个月执行一次(每月的时间间隔不是固定值),而 CalendarIntervalTrigger 支持的间隔单位有 秒,分钟,小时,天,月,年,星期。
- 支持不是固定长度的间隔,比如间隔为月和年,劣势是精度只能到秒。
- 适合的任务类似于:9:00 开始执行,并且以后每周 9:00 执行一次。
- interval 执行间隔。
- intervalUnit 执行间隔的单位(秒,分钟,小时,天,月,年,星期)。
calendarIntervalSchedule()
.withIntervalInDays(1) //每天执行一次
.build();
calendarIntervalSchedule()
.withIntervalInWeeks(1) //每周执行一次
.build();
DailyTimeIntervalTrigger
- 指定每天的某个时间段内,以一定的时间间隔执行任务。并且可以支持指定星期。
- 适合的任务类似于:指定每天 9:00 至 18:00 ,每隔 70 秒执行一次,并且只要周一至周五执行。
- startTimeOfDay 每天开始时间。
- endTimeOfDay 每天结束时间。
- daysOfWeek 需要执行的星期。
- interval 执行间隔。
- intervalUnit 执行间隔的单位(秒,分钟,小时,天,月,年,星期)。
- repeatCount 重复次数。
ailyTimeIntervalSchedule()
.startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) //第天9:00开始
.endingDailyAt(TimeOfDay.hourAndMinuteOfDay(16, 0)) //16:00 结束
.onDaysOfTheWeek(MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY) //周一至周五执行
.withIntervalInHours(1) //每间隔1小时执行一次
.withRepeatCount(100) //最多重复100次(实际执行100+1次)
.build();
dailyTimeIntervalSchedule()
.startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) //第天9:00开始
.endingDailyAfterCount(10) //每天执行10次,这个方法实际上根据 startTimeOfDay+interval*count 算出 endTimeOfDay
.onDaysOfTheWeek(MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY) //周一至周五执行
.withIntervalInHours(1) //每间隔1小时执行一次
.build();
CronTrigger
- 适合于更复杂的任务,它支持类型于 Linux Cron 的语法(并且更强大)。
- 适合的任务类似于:每天 0:00,9:00,18:00 各执行一次。
- Cron 表达式。
cronSchedule("0 0/2 8-17 * * ?") // 每天8:00-17:00,每隔2分钟执行一次
.build();
cronSchedule("0 30 9 ? * MON") // 每周一,9:30执行一次
.build();
weeklyOnDayAndHourAndMinute(MONDAY,9, 30) //等同于 0 30 9 ? * MON
.build();
Cron 表达式
位置 | 时间域 | 允许值 | 特殊值 |
---|---|---|---|
1 | 秒 | 0-59 | , - * / |
2 | 分钟 | 0-59 | , - * / |
3 | 小时 | 0-23 | , - * / |
4 | 日期 | 1-31 | , - * ? / L W C |
5 | 月份 | 1-12 | , - * / |
6 | 星期 | 1-7 | , - * ? / L C # |
7 | 年份(可选) | 1-31 | , - * / |
- 星号(*):可用在所有字段中,表示对应时间域的每一个时刻,例如在分钟字段时,表示 " 每分钟 "。
- 问号(?):该字符只在日期和星期字段中使用,它通常指定为 " 无意义的值 ",相当于点位符。
- 减号(-):表达一个范围,如在小时字段中使用 " 10-12 ",则表示从 10 到 12 点,即 10,11,12。
- 逗号(,):表达一个列表值,如在星期字段中使用 " MON,WED,FRI ",则表示星期一,星期三和星期五。
- 斜杠(/):x/y 表达一个等步长序列,x 为起始值,y 为增量步长值。如在分钟字段中使用 0/15,则表示为 0,15,30 和 45 秒,而 5/15 在分钟字段中表示 5,20,35,50,也可以使用 */y,等同于 0/y。
- L:该字符只在日期和星期字段中使用,代表 " Last " 的意思,但它在两个字段中意思不同。
- L 在日期字段中,表示这个月份的最后一天,如一月的 31 号,非闰年二月的 28 号。
- L 在星期子段中,表示星期六,等同于 7。如果 L 出现在星期字段里,而且在前面有一个数值 X,则表示 " 这个月的最后 X 天 ",例如,6L 表示该月的最后星期五。
- W:该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如 15W 表示离该月 15 号最近的工作日,如果该月 15 号是星期六,则匹配 14 号星期五;如果 15 日是星期日,则匹配 16 号星期一;如果 15 号是星期二,那结果就是 15 号星期二。但必须注意关联的匹配日期不能够跨月,如指定 1W,如果 1 号是星期六,结果匹配的是 3 号星期一,而非上个月最后的那天。W 字符串只能指定单一日期,而不能指定日期范围。
- LW 组合:在日期字段可以组合使用 LW,它的意思是当月的最后一个工作日。
- 井号(#):该字符只能在星期字段中使用,表示当月某个工作日。如 6#3 表示当月的第三个星期五(6 表示星期五,#3 表示当前的第三个),而 4#5 表示当月的第五个星期三,假设当月没有第五个星期三,忽略不触发。
- C:该字符只在日期和星期字段中使用,代表 " Calendar " 的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。例如 5C 在日期字段中就相当于日历 5 日以后的第一天。1C 在星期字段中相当于星期日后的第一天。
- Cron 表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感。
表示式 | 说明 |
---|---|
0 0 12 * * ? | 每天 12 点运行 |
0 15 10 ? * * | 每天 10:15 运行 |
0 15 10 * * ? | 每天 10:15 运行 |
0 15 10 * * ? * | 每天 10:15 运行 |
0 15 10 * * ? 2008 | 在 2008 年的每天 10:15 运行 |
0 * 14 * * ? | 每天 14 点到 15 点之间每分钟运行一次,开始于 14:00,结束于 14:59。 |
0 0/5 14 * * ? | 每天 14 点到 15 点每 5 分钟运行一次,开始于 14:00,结束于 14:55。 |
0 0/5 14,18 * * ? | 每天 14 点到 15 点每 5 分钟运行一次,此外每天 18 点到 19 点每 5 钟也运行一次。 |
0 0-5 14 * * ? | 每天 14:00 点到 14:05,每分钟运行一次。 |
0 10,44 14 ? 3 WED | 3 月每周三的 14:10 分到 14:44,每分钟运行一次。 |
0 15 10 ? * MON-FRI | 每周一,二,三,四,五的 10:15 分运行。 |
0 15 10 15 * ? | 每月 15 日 10:15 分运行。 |
0 15 10 L * ? | 每月最后一天 10:15 分运行。 |
0 15 10 ? * 6L | 每月最后一个星期五 10:15 分运行。 |
0 15 10 ? * 6L 2007-2009 | 在 2007,2008,2009 年每个月的最后一个星期五的 10:15 分运行。 |
0 15 10 ? * 6#3 | 每月第三个星期五的 10:15 分运行。 |
2.3 调度器
Scheduler
- 调度器,根据 Trigger,定时定频率执行 JobDetail 信息,和 Job 绑定在一起,是 Quartz Scheduler 的主要接口,代表一个独立运行容器。调度程序维护 JobDetails 和触发器的注册表。一旦注册,调度程序负责执行作业,让他们的相关联的触发器触发(当他们的预定时间到达时)。
- 根据 Trigger 触发器调度 Job,可以配置后台运行的线程、存储信息方式等,创建方式有两种。
- StdSchedulerFactory 通过 xml 或者 properties 进行声明式配置属性。
- DirectSchedulerFactory 通过代码配置属性。
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
DirectSchedulerFactory factory = DirectSchedulerFactory.getInstance();
Scheduler scheduler = factory.getScheduler();
- Scheduler 的生命期,从 SchedulerFactory 创建时开始,到 Scheduler 调用
shutdown()
方法时结束。 - Scheduler 被创建后,可以增加、删除和列举 Job 和 Trigger,以及执行其它与调度相关的操作(如暂停 Trigger)。
- Scheduler 只有在调用
start()
方法后,才会真正地触发 Trigger(即执行 Job)。 - Scheduler 包含两个重要组件:JobStore 和 ThreadPool。
- JobStore 存储运行时信息,包括 Trigger,Schduler,JobDetail,业务锁等。
- 有多种实现 RAMJob(内存实现),JobStoreTX(JDBC,事务由 Quartz 管理),JobStoreCMT(JDBC,使用容器事务),ClusteredJobStore(集群实现)、TerracottaJobStore。
- ThreadPool 为线程池,Quartz 有自己的线程池实现,所有任务的都会由线程池执行。
- JobStore 存储运行时信息,包括 Trigger,Schduler,JobDetail,业务锁等。
SchedulerFactory
- 提供用于获取调度程序实例的客户端可用句柄的机制。
- Scheduler 是一个接口类,所有的具体实现类都是通过 SchedulerFactory 工厂类实现。
ScheduleBuilder
- 通过 ScheduleBuilder 设置与 Trigger 相关的属性,不同的 Trigger 有不同的 ScheduleBuilder。
- SimpleScheduleBuilder。
- CalendarIntervalScheduleBuilder。
- DailyTimeIntervalScheduleBuilder。
- CronScheduleBuilder。
QuartzScheduler
- 是 Scheduler 接口的间接实现,包含调度 Jobs,注册 JobListener 实例等的方法。
2.4 JobStore
- JobStore 负责跟踪分发给调度器的所有 " 工作数据 ",包含作业、触发器、日历等。
- 通过 Quartz(通过配置)指定需要使用的 JobStore,在代码中应该使用调度器接口。
RAMJobStore
- 是最简单的使用的 JobStore,也是性能最好的(在 CPU 时间方面),因为所有数据保存在 RAM 中。
- 缺点是当应用程序结束(或崩溃)时所有的调度信息丢失,这意味着 RAMJobStore 不具有保持 job 和 trigger 持久化的能力。
- 使用 RAMJobStore 只需要在 Quartz 配置文件中添加。
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
JDBCJobStore
- 通过 JDBC 将所有数据保存在一个数据库中。
- JDBCJobStore 几乎适用于所有的数据库,广泛用于 Oracle,PostgreSQL,MySQL,MS SQLServer,HSQLDB,和 DB2。使用 JDBCJobStore 之前必须首先创建一系列 Quartz 要使用的表。
- 可以在 Quartz 发行版的 docs/dbTables 目录中找到表创建的 SQL 脚本。
- 所有的表都以前缀 " QRTZ_ " 开头(如表 " QRTZ_TRIGGERS " 和 " QRTZ_JOB_DETAIL ")。
- 前缀可在 Quartz 配置文件中设置。
- 使用不同的前缀可能有助于在同一个数据库中为多个调度器实例创建多个表集。
- 一旦创建了表,在配置和激活 JDBCJobStore 之前,需要决定应用程序使用的事务类型。
- 如果不需要将调度命令(例如添加和删除触发器)绑定到其他事务,那么可以使用 JobStoreTX 作为 JobStore(这是最常见的选择),让 Quartz 管理事务。
- 如果需要 Quartz 与其他事务一起工作(例如在 J2EE 应用服务器中),那么可以使用 JobStoreCMT,在这种情况下,Quartz 将让应用服务器容器管理事务。
- JDBCJobStore 可以设置数据源连接到数据库,可以使用几种不同的方法。
- 可以在 Quartz 配置文件属性中定义数据源。
- 让 Quartz 创建并管理数据源本身,通过提供数据库的所有连接信息。
- 让 Quartz 使用一个由 Quartz 在其内部运行的应用服务器管理的数据源,通过提供 JDBCJobStore JNDI 名称的数据源。
- 例如配置 JobStoreTX。
# 配置使用 JobStoreTx
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
# 选择 driver 委托
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 配置表名称前缀
org.quartz.jobStore.tablePrefix = QRTZ_
# 配置数据源
org.quartz.jobStore.dataSource = myDS
- 如果调度器很忙(即几乎总是执行与线程池大小相同的工作数,那么应该将数据源中的连接数设置为线程池 + 2 的大小)。
- org.quartz.jobStore.useProperties 配置参数可以设置为 true (默认为 false),以便指示 JDBCJobStore,JobDataMaps 中的所有值都是字符串,因此可以作为 key-value 对存储,而不是在 BLOB 列中以序列化形式存储更复杂的对象,从长远来看,这样做更安全,避免了类版本化问题。
TerracottaJobStore
- TerracottaJobStore 提供了一种无需数据库就可以进行缩放和健壮性的方法。这意味着数据库可以避免来自 Quartz 的负载,并且可以将所有的资源保存到应用程序的其他部分。
- TerracottaJobStore 可以是集群的或非集群的,在这两种情况下,都为工作数据提供了一个存储介质,在应用程序重新启动之间是持久的,因为数据存储在 Terracotta 服务器中。
- 性能比通过 JDBCJobStore(大约一个数量级更好)使用数据库要好,但是比 RAMJobStore 慢。
- 要使用 TerracottaJobStore 还需要添加一个额外的配置指定 Terracotta 服务器的位置。
org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore
org.quartz.jobStore.tcConfigUrl = localhost:9510
3. 配置说明
- 默认 quartz.properties 文件的加载顺序。
- Classpath:quartz.properties。
- org/quartz/quartz.properties(quartz lib)。
- 可以改变默认。
- 设置一个系统属性 org.quartz.properties 指向对应的 properties 文件。
- 可以在程序中显示指定。
-
StdSchedulerFactory.getScheduler()
之前使用StdSchedulerFactory.initialize(xx)
。
-
主调度程序 Scheduler 的配置
参数名 | 是否必须 | 类型 | 默认值 |
---|---|---|---|
org.quartz.scheduler.instanceName | N | string | 'QuartzScheduler' |
org.quartz.scheduler.instanceId | N | string | 'NON_CLUSTERED' |
org.quartz.scheduler.instanceIdGenerator.class | N | string (class name) | org.quartz.simpl.SimpleInstanceIdGenerator |
org.quartz.scheduler.threadName | N | string | instanceName+'_QuartzSchedulerThread' |
org.quartz.scheduler.makeSchedulerThreadDaemon | N | boolean | false |
org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer | N | boolean | false |
org.quartz.scheduler.idleWaitTime | N | string | 30000 |
org.quartz.scheduler.dbFailureRetryInterval | N | long | 15000 |
org.quartz.scheduler.classLoadHelper.class | N | string (class name) | org.quartz.simpl.CascadingClassLoadHelper |
org.quartz.scheduler.jobFactory.class | N | string (class name) | org.quartz.simpl.PropertySettingJobFactory |
org.quartz.context.key.SOME_KEY | N | string | none |
-
org.quartz.scheduler.instanceName
- 使用 StdSchedulerFactory 的
getScheduler()
方法创建的 scheduler 实例名称,在同一个程序中可以根据该名称来区分 scheduler。 - 如果是在集群环境中使用,集群环境下 " 逻辑 " 相同的 scheduler,必须使用同一个名称。
- 使用 StdSchedulerFactory 的
-
org.quartz.scheduler.instanceId
- scheduler 实例的标志 ID,必须是全局唯一的,即使在集群环境中 " 逻辑 " 相同的 scheduler。
- 或者可以使用 " SYS_PROP " 通过系统属性设置 ID。
-
org.quartz.scheduler.instanceIdGenerator.class
- 只有在 instanceId 设置为 AUTO 的时候才使用该属性设置。
- 默认情况下是基于 instanceId 和时间戳来自动生成的。
- 其他的 ID 生成器的实现包括
SystemPropertyInstanceIdGenerator
从系统属性获取和HostnameInstanceIdGenerator
使用主机名(InetAddress.getLocalHost().getHostName()
)。 - 也可以自定义生成方式。
-
org.quartz.scheduler.threadName
- 指定线程名,默认会自动使用 org.quartz.scheduler.instanceName 属性值加上后缀字符串 " _QuartzSchedulerThread "。
-
org.quartz.scheduler.makeSchedulerThreadDaemon
- 指定 scheduler 的主线程是否为后台线程。
-
org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer
- 指定 Quartz 生成的线程是否继承初始化线程的上下文类加载器。
- 这会影响 Quartz 的主调度线程、JDBCJobStore 的 " 熄火 " 处理线程、集群回复线程和线程池里的线程。 将该值设置为 true 可以帮助类加载,JNDI 查找,并在应用程序服务器上使用 Quartz 等相关问题。
-
org.quartz.scheduler.idleWaitTime
- 在调度程序空闲的时候,重复查询是否有可用触发器的等待时间,通常并不会设置为 true,除非是用 XA 事务,并且延迟触发会导致问题的场景。
- 5000ms 以下是不推荐的,因为它会导致过多的数据库查询。
- 1000ms 以下是非法的。
-
org.quartz.scheduler.dbFailureRetryInterval
- 连接超时重试连接的间隔。使用 RamJobStore 时,该参数无效。
-
org.quartz.scheduler.classLoadHelper.class
- 默认最可靠的方式就是指定
org.quartz.simpl.CascadingClassLoadHelper
,没必要指定其他类。
- 默认最可靠的方式就是指定
-
org.quartz.scheduler.jobFactory.class
- 指定 JobFactory 的类(接口)名称。
- 负责实例化 jobClass。
- 默认是
org.quartz.simpl.PropertySettingJobFactory
,只是在 job 被执行的时候简单调用newInstance()
实例化一个 job 类。PropertySettingJobFactory
会使用反射机制通过 SchedulerContext、Job、Trigger 和 JobDataMaps 设置 job bean 的属性。
使用 JTA 事务,可以设置事务相关的属性
参数名 | 是否必须 | 类型 | 默认值 |
---|---|---|---|
org.quartz.scheduler.userTransactionURL | N | string (url) | 'java:comp/UserTransaction' |
org.quartz.scheduler.wrapJobExecutionInUserTransaction | N | boolean | false |
org.quartz.scheduler.skipUpdateCheck | N | boolean | false |
org.quartz.scheduler.batchTriggerAcquisitionMaxCount N | int | 1 | |
org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow | N | long | 0 |
-
org.quartz.scheduler.userTransactionURL
- 设置 Quartz 能够加载 UserTransaction 事务的 JNDI 的 URL。
- 默认值是 java:comp/UserTransaction。Websphere 的用户可能会设置为 jta/usertransaction。
- 只有在 Quartz 使用 JobStoreCMT 的时候,才会使用该属性,并且org.quartz.scheduler.wrapJobExecutionInUserTransaction 也会设置为 true。
-
org.quartz.scheduler.wrapJobExecutionInUserTransaction
- 如果想使用 Quartz 在执行一个 job 前使用 UserTransaction,则应该设置该属性为 true。
- job 执行完、在 JobDataMap 改变之后事务会提交。默认值是 false。
- 在 job 类中使用 @ExecuteInJTATransaction 注解, 可以控制 job 是否使用事务。
-
org.quartz.scheduler.skipUpdateCheck
- 建议设置 org.terracotta.quartz.skipUpdateCheck=true 不会在程序运行中还去检查 Quartz 是否有版本更新。
-
org.quartz.scheduler.batchTriggerAcquisitionMaxCount
- 允许调度程序一次性触发的触发器数量。默认值是1。
- 值越大一次性触发的任务就可以越多,但是在集群环境下,不建议设置为很大的值。
- 如果值 > 1,并且使用了 JDBC JobStore,org.quartz.jobStore.acquireTriggersWithinLock 属性必须设置为 true,以避免 " 弄脏 " 数据。
-
org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow
- 允许触发器被获取并在其预定的触发时间之前触发的数量。默认值 0。
Quartz 线程池的配置
参数名 | 是否必须 | 类型 | 默认值 |
---|---|---|---|
org.quartz.threadPool.class | Y | string (class name) | null |
org.quartz.threadPool.threadCount | Y | string | -1 |
org.quartz.threadPool.threadPriority | N | int | Thread.NORM_PRIORITY (5) |
-
org.quartz.threadPool.class
- 线程池的名字。可以使用 Quartz 的 org.quartz.simpl.SimpleThreadPool。
-
org.quartz.threadPool.threadCount
- 指定线程数量。一般 1-100 足以满足应用需求。
-
org.quartz.threadPool.threadPriority
- 线程优先级,Thread.MIN_PRIORITY(1) 和 Thread.MAX_PRIORITY(10) 之间,默认 Thread.NORM_PRIORITY(5)。
全局触发器的监听器配置
- org.quartz.triggerListener.NAME.class = com.foo.MyListenerClass
- org.quartz.triggerListener.NAME.propName = propValue
- org.quartz.triggerListener.NAME.prop2Name = prop2Value
全局 job 的监听器配置
- org.quartz.jobListener.NAME.class = com.foo.MyListenerClass
- org.quartz.jobListener.NAME.propName = propValue
- org.quartz.jobListener.NAME.prop2Name = prop2Value
jobStore 的配置
- org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
- org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreCMT
- org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
数据源的配置
- org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver
- org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@10.0.1.23:1521:demodb
- org.quartz.dataSource.myDS.user = myUser
- org.quartz.dataSource.myDS.password = myPassword
- org.quartz.dataSource.myDS.maxConnections = 30
从一个应用服务中获取数据源配置
- org.quartz.dataSource.myOtherDS.jndiURL=jdbc/myDataSource
- org.quartz.dataSource.myOtherDS.java.naming.factory.initial=com.evermind.server.rmi.RMIInitialContextFactory
- org.quartz.dataSource.myOtherDS.java.naming.provider.url=ormi://localhost
- org.quartz.dataSource.myOtherDS.java.naming.security.principal=admin
- org.quartz.dataSource.myOtherDS.java.naming.security.credentials=123
用例
#==============================================================
#Configure Main Scheduler Properties
#配置文件KEY说明参考:org.quartz.impl.StdSchedulerFactory
#默认配置文件名:quartz.properties,如果未放入配置,则使用org/quartz/quartz.properties
#==============================================================
#调度器实例名称,默认值:QuartzScheduler
org.quartz.scheduler.instanceName = quartzScheduler
#调度器实例编号自动生成,默认值:NON_CLUSTERED,可设置值:AUTO
org.quartz.scheduler.instanceId = AUTO
#==============================================================
#Configure ThreadPool
#==============================================================
#线程池实现类
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
#执行最大并发线程数量
org.quartz.threadPool.threadCount = 5
#线程优先级
org.quartz.threadPool.threadPriority = 5
#==============================================================
#Configure JobStore
#==============================================================
#持久化方式配置
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#持久化方式配置数据驱动,标准数据库(如MYSQL)
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#quartz相关数据表前缀名
org.quartz.jobStore.tablePrefix = QRTZ_
#开启分布式部署
org.quartz.jobStore.isClustered = true
#分布式节点有效性检查时间间隔,单位:毫秒
org.quartz.jobStore.clusterCheckinInterval = 10000
#配置数据源
org.quartz.jobStore.dataSource = myDS
#==============================================================
#Configure DataSource
#==============================================================
org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.myDS.URL = jdbc:mysql://127.0.0.1:3306/51token2?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true
org.quartz.dataSource.myDS.user = root
org.quartz.dataSource.myDS.password = test
org.quartz.dataSource.myDS.maxConnections = 5
org.quartz.dataSource.myDS.validationQuery=select 0 from dual
参考资料
https://www.cnblogs.com/chen-lhx/p/5581129.html
https://www.cnblogs.com/sihuanian/p/5010872.html
https://blog.csdn.net/weixin_42139757/article/details/81321383
https://blog.csdn.net/u010648555/article/details/54863144
https://www.w3cschool.cn/quartz_doc/
https://www.cnblogs.com/zhanghaoliang/p/7886110.html