Quartz使用文档,使用Quartz实现动态任务,Spring集成Quartz,Quartz集群部署,Quartz源码分析
文章目录
- 一、Quartz 基本介绍
- 二、Quartz Java 编程
- 1、文档
- 2、引入依赖
- 3、入门案例
- 4、默认配置文件
- 三、Quartz 重要组件
- 1、Quartz架构体系
- 2、JobDetail
- 3、Trigger
- (1)代码实例
- (2)SimpleTrigger
- (3)CalendarIntervalTrigger
- (4)DailyTimeIntervalTrigger
- (5)CronTrigger
- (6)基于 Calendar 的排除规则
- 4、Scheduler
- 5、Listener
- (1)JobListener
- (2)TriggerListener
- (3)SchedulerListener
- 6、JobStore
- (1)RAMJobStore
- (2)JDBCJobStore
- 四、Quartz集成Spring
- 1、xml配置
- 2、注解配置
- 五、使用Quartz实现动态调度
- 六、Quartz 集群部署
- 1、为什么需要集群?
- 2、集群需要解决的问题
- 3、集群配置与验证
- 七、Quartz 调度原理与源码分析
一、Quartz 基本介绍
官网:http://www.quartz-scheduler.org/
源码地址:https://github.com/quartz-scheduler/quartz
Quartz 的意思是石英,像石英表一样精确。
Quartz is a richly featured, open source job scheduling library that can be integrated within virtually any Java application - from the smallest stand-alone application to the largest e-commerce system. Quartz can be used to create simple or complex schedules for executing tens, hundreds, or even tens-of-thousands of jobs; jobs whose tasks are defined as standard Java components that may execute virtually anything you may program them to do. The Quartz Scheduler includes many enterprise-class features, such as support for JTA transactions and clustering.
Quatz 是一个特性丰富的,开源的任务调度库,它几乎可以嵌入所有的 Java 程序,从很小的独立应用程序到大型商业系统。Quartz 可以用来创建成百上千的简单的或者复杂的任务,这些任务可以用来执行任何程序可以做的事情。Quartz 拥有很多企业级的特性,包括支持 JTA 事务和集群。
Quartz 是一个老牌的任务调度系统,98 年构思,01 年发布到 sourceforge。现在更新比较慢,因为已经非常成熟了。
Quartz 的目的就是让任务调度更加简单,开发人员只需要关注业务即可。他是用 Java 语言编写的(也有.NET 的版本)。Java 代码能做的任何事情,Quartz 都可以调度。
特点:
精确到毫秒级别的调度
可以独立运行,也可以集成到容器中
支持事务(JobStoreCMT )
支持集群
支持持久化
二、Quartz Java 编程
1、文档
http://www.quartz-scheduler.org/documentation/quartz-2.3.0/
http://www.quartz-scheduler.org/documentation/quartz-2.3.0/quick-start.html
2、引入依赖
<dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz</artifactId><version>2.3.0</version>
</dependency>
3、入门案例
import java.text.SimpleDateFormat;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
// 定义job,实现Job接口
public class MyJob1 implements Job {public void execute(JobExecutionContext context) throws JobExecutionException {Date date = new Date();SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 获取job的自定义参数JobDataMap dataMap = context.getJobDetail().getJobDataMap();System.out.println( " " + sf.format(date) + " 任务1执行了," + dataMap.getString("cxf"));}
}
import com.test.job.MyJob1;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
// 创建定时任务
public class MyScheduler {public static void main(String[] args) throws SchedulerException {// JobDetail 把 Job 进一步包装成 JobDetailJobDetail jobDetail = JobBuilder.newJob(MyJob1.class)// 必须要指定 JobName 和 groupName,两个合起来是唯一标识符.withIdentity("job1", "group1") // 任务名 + 任务组 同一个组中包含许多任务// 可以携带 KV 的数据(JobDataMap),用于扩展属性,在运行的时候可以从 context获取到.usingJobData("cxf","加油") // 添加额外自定义参数.usingJobData("moon",5.21F).build();// TriggerTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1") // 定义trigger名 + 组名.startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule() // 简单触发器.withIntervalInSeconds(2) // 2秒一次.repeatForever()) // 持续不断执行.build();// SchedulerFactorySchedulerFactory factory = new StdSchedulerFactory();// Scheduler 一定是单例的Scheduler scheduler = factory.getScheduler();// 绑定关系是1:N ,把 JobDetail 和 Trigger绑定,注册到容器中scheduler.scheduleJob(jobDetail, trigger);// Scheduler 先启动后启动无所谓,只要有 Trigger 到达触发条件,就会执行任务scheduler.start();}
}
4、默认配置文件
org.quartz包下,有一个默认的配置文件,quartz.properties。当我们没有定义一个同名的配置文件的时候,就会使用默认配置文件里面的配置。
想要覆盖默认的配置,在resources目录下新建quartz.properties
即可。
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#
# 调度器名称
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
# 调度器线程池的实现
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: trueorg.quartz.jobStore.misfireThreshold: 60000org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
三、Quartz 重要组件
在线生成cron表达式网站:https://cron.qqe2.com/
1、Quartz架构体系
2、JobDetail
我们创建一个实现 Job 接口的类,使用 JobBuilder 包装成 JobDetail,它可以携带KV 的数据。
// JobDetail
JobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1") // 任务名 + 任务组 同一个组中包含许多任务.usingJobData("cxf","加油") // 添加额外自定义参数.usingJobData("moon",5.21F).build();
3、Trigger
定义任务的触发规律,Trigger,使用 TriggerBuilder 来构建。
JobDetail 跟 Trigger 是 1:N 的关系。
Trigger 接口在 Quartz 有 4 个继承的子接口:
子接口 | 描述 | 特点 |
---|---|---|
SimpleTrigger | 简单触发器 | 固定时刻或时间间隔,毫秒 |
CalendarIntervalTrigger | 基于日历的触发器 | 比简单触发器更多时间单位,支持非固定时间的触发,例如一年可能 365/366天,一个月可能 28/29/30/31天 |
DailyTimeIntervalTrigger | 基于日期的触发器 | 每天的某个时间段 |
CronTrigger | 基于 Cron 表达式的触发器 |
其中MutableTrigger 和 CoreTrigger 最终也是用到以上四个类的实现类。
(1)代码实例
/*** CalendarIntervalTrigger*/
Trigger calendarIntervalTrigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "test").startNow().withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withIntervalInDays(1)) // 每天执行一次.build();/*** DailyTimeIntervalTrigger*/
Trigger dailyTimeIntervalTrigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "test").startNow().withSchedule(DailyTimeIntervalScheduleBuilder.dailyTimeIntervalSchedule().startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) //第天9:00开始.endingDailyAt(TimeOfDay.hourAndMinuteOfDay(16, 0)) //16:00 结束.onDaysOfTheWeek(1,2,3,4,5) //周一至周五执行.withIntervalInHours(1) //每间隔1小时执行一次.withRepeatCount(100))//最多重复100次(实际执行100+1次).build();
(2)SimpleTrigger
SimpleTrigger 可以定义固定时刻或者固定时间间隔的调度规则(精确到毫秒)。
例如:每天 9 点钟运行;每隔 30 分钟运行一次。
(3)CalendarIntervalTrigger
CalendarIntervalTrigger 可以定义更多时间单位的调度需求,精确到秒。
好处是不需要去计算时间间隔,比如 1 个小时等于多少毫秒。
例如每年、每个月、每周、每天、每小时、每分钟、每秒。
每年的月数和每个月的天数不是固定的,这种情况也适用。
(4)DailyTimeIntervalTrigger
每天的某个时间段内,以一定的时间间隔执行任务。
例如:每天早上 9 点到晚上 9 点,每隔半个小时执行一次,并且只在周一到周六执行。
(5)CronTrigger
CronTirgger 可以定义基于 Cron 表达式的调度规则,是最常用的触发器类型。
Cron 表达式:
星号(*):可用在所有字段中,表示对应时间域的每一个时刻,例如,在分钟字段时,表示“每分钟”;
问号(?):该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于点位符;
减号(-):表达一个范围,如在小时字段中使用“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 表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感。
(6)基于 Calendar 的排除规则
我们有一些在什么时间不执行的需求,比如:理财周末和法定假日购买不计息;证券公司周末和法定假日休市。需要怎么实现呢?
如果要在触发器的基础上,排除一些时间区间不执行任务
,就要用到 Quartz 的Calendar 类(注意不是 JDK 的 Calendar)。可以按年、月、周、日、特定日期、Cron表达式排除。
调用 Trigger 的 modifiedByCalendar()添加到触发器中,并且调用调度器的addCalendar()方法注册排除规则。
Calendar | 名称 | 用法 |
---|---|---|
BaseCalendar | 为高级的 Calendar 实现了基本的功能,实现了 org.quartz.Calendar 接口 | |
AnnualCalendar | 排除年中一天或多天 | |
CronCalendar | 日历的这种实现排除了由给定的 CronExpression 表达的时间集合。 例如,您可以使用此日历使用表达式“* * 0-7,18-23?* *”每天排除所有营业时间(上午 8 点至下午 5 点)。 如果 CronTrigger 具有给定的 cron 表达式并且与具有相同表达式的 CronCalendar 相关联,则日历将排除触发器包含的所有时间,并且它们将彼此抵消。 | |
DailyCalendar | 您可以使用此日历来排除营业时间(上午 8 点 - 5 点)每天。 每个DailyCalendar 仅允许指定单个时间范围,并且该时间范围可能不会跨越每日边界(即,您不能指定从上午 8 点至凌晨 5 点的时间范围)。 如果属性 invertTimeRange 为 false(默认),则时间范围定义触发器不允许触发的时间范围。 如果 invertTimeRange 为 true,则时间范围被反转 - 也就是排除在定义的时间范围之外的所有时间。 | |
HolidayCalendar | 特别的用于从 Trigger 中排除节假日 | |
MonthlyCalendar | 排除月份中的指定数天,例如,可用于排除每月的最后一天 | |
WeeklyCalendar | 排除星期中的任意周几,例如,可用于排除周末,默认周六和周日 |
// 代码实例
public class CalendarDemo {public static void main(String[] args) throws Exception {SchedulerFactory sf = new StdSchedulerFactory();Scheduler scheduler = sf.getScheduler();scheduler.start();// 定义日历AnnualCalendar holidays = new AnnualCalendar();// 排除某一日Calendar testDay = (Calendar) new GregorianCalendar(2023, 8, 8);holidays.setDayExcluded(testDay, true);// 排除中秋节Calendar midAutumn = new GregorianCalendar(2023, 9, 29);holidays.setDayExcluded(midAutumn, true);// 排除圣诞节Calendar christmas = new GregorianCalendar(2023, 12, 25);holidays.setDayExcluded(christmas, true);// 调度器添加日历scheduler.addCalendar("holidays", holidays, false, false);JobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "test").usingJobData("cxf","加油").build();Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "test").startNow().modifiedByCalendar("holidays").withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()).build();Date firstRunTime = scheduler.scheduleJob(jobDetail, trigger);System.out.println(jobDetail.getKey() + " 第一次触发: " + firstRunTime);}
}
4、Scheduler
Scheduler就是Quartz的调度器,是 Quartz 的指挥官,由 StdSchedulerFactory 产生。它是单例
的。并且是 Quartz 中最重要的 API,默认是实现类是 StdScheduler,里面包含了一个QuartzScheduler。QuartzScheduler 里面又包含了一个 QuartzSchedulerThread。
Scheduler 中的方法主要分为三大类,这些方法非常重要,可以实现任务的动态调度
:
1)操作调度器本身,例如调度器的启动 start()、调度器的关闭 shutdown()。
2)操作 Trigger,例如 pauseTriggers()、resumeTrigger()。
3)操作 Job,例如 scheduleJob()、unscheduleJob()、rescheduleJob()。
5、Listener
我们有这么一种需求,在每个任务运行结束之后发送通知给运维管理员。那是不是要在每个任务的最后添加一行代码呢?这种方式对原来的代码造成了入侵,不利于维护。如果代码不是写在任务代码的最后一行,怎么知道任务执行完了呢?或者说,怎么监测到任务的生命周期呢?
借鉴观察者模式:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖它的对象都会得到通知并自动更新。
Quartz 中提供了三种 Listener,监听 Scheduler 的,监听 Trigger 的,监听 Job 的
。只需要创建类实现相应的接口,并在 Scheduler 上注册 Listener,便可实现对核心对象的监听。
(1)JobListener
使用JobListener,只需要定义一个Listener,实现JobListener接口,并通过Listener管理器将Listener注册即可。
JobListener有四个重要方法:
方法 | 作用或执行实际 |
---|---|
getName() | 返回 JobListener 的名称 |
jobToBeExecuted() | Scheduler 在 JobDetail 将要被执行时调用这个方法 |
jobExecutionVetoed() | Scheduler 在 JobDetail 即将被执行,但又被 TriggerListener 否决了时调用这个方法 |
jobWasExecuted() | Scheduler 在 JobDetail 被执行之后调用这个方法 |
// 代码实例
// 自定义Listener
public class MyJobListener implements JobListener {public String getName() {String name = getClass().getSimpleName();System.out.println( "Method 111111 :"+ "获取到监听器名称:"+name);return name;}public void jobToBeExecuted(JobExecutionContext context) {String jobName = context.getJobDetail().getKey().getName();System.out.println("Method 222222 :"+ jobName + " ——任务即将执行 ");}public void jobExecutionVetoed(JobExecutionContext context) {String jobName = context.getJobDetail().getKey().getName();System.out.println("Method 333333 :"+ jobName + " ——任务被否决 ");}public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {String jobName = context.getJobDetail().getKey().getName();System.out.println("Method 444444 :"+ jobName + " ——执行完毕 ");System.out.println("------------------");}
}// 测试类
public class MyJobListenerTest {public static void main(String[] args) throws SchedulerException {// JobDetailJobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1").build();// TriggerTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();// SchedulerFactorySchedulerFactory factory = new StdSchedulerFactory();// SchedulerScheduler scheduler = factory.getScheduler();scheduler.scheduleJob(jobDetail, trigger);// 创建并注册一个全局的Job Listener// 工具类:ListenerManager,用于添加、获取、移除监听器// 工具类:Matcher,主要是基于 groupName 和 keyName 进行匹配。scheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());scheduler.start();}
}
(2)TriggerListener
方法 | 作用或执行实际 |
---|---|
getName() | 返回监听器的名称 |
triggerFired() | Trigger 被触发,Job 上的 execute() 方法将要被执行时,Scheduler 就调用这个方法 |
vetoJobExecution() | 在 Trigger 触 发 后 , Job 将 要 被 执 行 时 由 Scheduler 调 用 这 个 方 法 。TriggerListener 给了一个选择去否决 Job 的执行。假如这个方法返回 true,这个 Job 将不会为此次 Trigger 触发而得到执行 |
triggerMisfired() | Trigger 错过触发时调用 |
triggerComplete() | Trigger 被触发并且完成了 Job 的执行时,Scheduler 调用这个方法 |
// 自定义TriggerListener
public class MyTriggerListener implements TriggerListener {private String name;public MyTriggerListener(String name) {this.name = name;}public String getName() {return name;}// Trigger 被触发,Job 上的 execute() 方法将要被执行时public void triggerFired(Trigger trigger, JobExecutionContext context) {String triggerName = trigger.getKey().getName();System.out.println("Method 11111 " + triggerName + " was fired");}// 在 Trigger 触发后,Job 将要被执行时由 Scheduler 调用这个方法// 返回true时,这个任务不会被触发public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {String triggerName = trigger.getKey().getName();System.out.println("Method 222222 " + triggerName + " was not vetoed");return false;}public void triggerMisfired(Trigger trigger) {String triggerName = trigger.getKey().getName();System.out.println("Method 333333 " + triggerName + " misfired");}public void triggerComplete(Trigger trigger, JobExecutionContext context,Trigger.CompletedExecutionInstruction triggerInstructionCode) {String triggerName = trigger.getKey().getName();System.out.println("Method 444444 " + triggerName + " is complete");System.out.println("------------");}
}
/*** 测试监听器*/
public class MyTriggerListenerTest {public static void main(String[] args) throws SchedulerException {// JobDetailJobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1").build();// TriggerTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever()).build();// SchedulerFactorySchedulerFactory factory = new StdSchedulerFactory();// SchedulerScheduler scheduler = factory.getScheduler();scheduler.scheduleJob(jobDetail, trigger);// 创建并注册一个全局的Trigger Listenerscheduler.getListenerManager().addTriggerListener(new MyTriggerListener("myListener1"), EverythingMatcher.allTriggers());// 创建并注册一个局部的Trigger Listenerscheduler.getListenerManager().addTriggerListener(new MyTriggerListener("myListener2"), KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1", "gourp1")));// 创建并注册一个特定组的Trigger ListenerGroupMatcher<TriggerKey> matcher = GroupMatcher.triggerGroupEquals("gourp1");scheduler.getListenerManager().addTriggerListener(new MyTriggerListener("myListener3"), matcher);scheduler.start();}}
(3)SchedulerListener
SchedulerListener 方法比较多。
// 自定义SchedulerListener
public class MySchedulerListener implements SchedulerListener {public void jobScheduled(Trigger trigger) {String jobName = trigger.getJobKey().getName();System.out.println( jobName + " has been scheduled");}public void jobUnscheduled(TriggerKey triggerKey) {System.out.println(triggerKey + " is being unscheduled");}public void triggerFinalized(Trigger trigger) {System.out.println("Trigger is finished for " + trigger.getJobKey().getName());}public void triggerPaused(TriggerKey triggerKey) {System.out.println(triggerKey + " is being paused");}public void triggersPaused(String triggerGroup) {System.out.println("trigger group "+triggerGroup + " is being paused");}public void triggerResumed(TriggerKey triggerKey) {System.out.println(triggerKey + " is being resumed");}public void triggersResumed(String triggerGroup) {System.out.println("trigger group "+triggerGroup + " is being resumed");}public void jobAdded(JobDetail jobDetail) {System.out.println(jobDetail.getKey()+" is added");}public void jobDeleted(JobKey jobKey) {System.out.println(jobKey+" is deleted");}public void jobPaused(JobKey jobKey) {System.out.println(jobKey+" is paused");}public void jobsPaused(String jobGroup) {System.out.println("job group "+jobGroup+" is paused");}public void jobResumed(JobKey jobKey) {System.out.println(jobKey+" is resumed");}public void jobsResumed(String jobGroup) {System.out.println("job group "+jobGroup+" is resumed");}public void schedulerError(String msg, SchedulerException cause) {System.out.println(msg + cause.getUnderlyingException().getStackTrace());}public void schedulerInStandbyMode() {System.out.println("scheduler is in standby mode");}public void schedulerStarted() {System.out.println("scheduler has been started");}public void schedulerStarting() {System.out.println("scheduler is being started");}public void schedulerShutdown() {System.out.println("scheduler has been shutdown");}public void schedulerShuttingdown() {System.out.println("scheduler is being shutdown");}public void schedulingDataCleared() {System.out.println("scheduler has cleared all data");}
}/*** 测试监听器*/
public class MySchedulerListenerTest {public static void main(String[] args) throws SchedulerException {// JobDetailJobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1").build();// TriggerTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();// SchedulerFactorySchedulerFactory factory = new StdSchedulerFactory();// SchedulerScheduler scheduler = factory.getScheduler();scheduler.scheduleJob(jobDetail, trigger);// 创建Scheduler Listenerscheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());scheduler.start();}}
6、JobStore
Jobstore 用来存储任务和触发器相关的信息,例如所有任务的名称、数量、状态等等。Quartz 中有两种存储任务的方式,一种在在内存
,一种是在数据库
。
在配置文件中,默认配置的就是RAMJobStore:
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
(1)RAMJobStore
Quartz 默认的 JobStore 是 RAMJobstore,也就是把任务和触发器信息运行的信息存储在内存中,用到了 HashMap、TreeSet、HashSet 等等数据结构。
如果程序崩溃或重启,所有存储在内存中的数据都会丢失。所以我们需要把这些数据持久化到磁盘。
(2)JDBCJobStore
JDBCJobStore 可以通过 JDBC 接口,将任务运行数据保存在数据库中。
JDBC 的实现方式有两种,JobStoreSupport 类的两个子类:
JobStoreTX:在独立的程序中使用,自己管理事务,不参与外部事务。
JobStoreCMT:(Container Managed Transactions (CMT),如果需要容器管理事务时,使用它。
JDBCJobStore的使用:
1)建表。
在quartz包的src\org\quartz\impl\jdbcjobstore目录下,保存着实现了JDBC的常用数据库的建表语句,执行即可:
2)配置quartz.properties文件
# jobStore 持久化配置
#存储方式使用JobStoreTX,也就是数据库
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 使用quartz.properties,不使用默认配置
org.quartz.jobStore.useProperties:true
#数据库中quartz表的表名前缀
org.quartz.jobStore.tablePrefix:QRTZ_
org.quartz.jobStore.dataSource:myDS#配置数据源
org.quartz.dataSource.myDS.driver:com.mysql.jdbc.Driver
org.quartz.dataSource.myDS.URL:jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf8
org.quartz.dataSource.myDS.user:root
org.quartz.dataSource.myDS.password:123456
org.quartz.dataSource.myDS.validationQuery=select 0 from dual
表名与作用:
表名 | 作用 |
---|---|
QRTZ_BLOB_TRIGGERS | Trigger 作为 Blob 类型存储 |
QRTZ_CALENDARS | 存储 Quartz 的 Calendar 信息 |
QRTZ_CRON_TRIGGERS | 存储 CronTrigger,包括 Cron 表达式和时区信息 |
QRTZ_FIRED_TRIGGERS | 存储与已触发的 Trigger 相关的状态信息,以及相关 Job 的执行信息 |
QRTZ_JOB_DETAILS | 存储每一个已配置的 Job 的详细信息 |
QRTZ_LOCKS | 存储程序的悲观锁的信息 |
QRTZ_PAUSED_TRIGGER_GRPS | 存储已暂停的 Trigger 组的信息 |
QRTZ_SCHEDULER_STATE | 存储少量的有关 Scheduler 的状态信息,和别的 Scheduler 实例 |
QRTZ_SIMPLE_TRIGGERS | 存储 SimpleTrigger 的信息,包括重复次数、间隔、以及已触的次数 |
QRTZ_SIMPROP_TRIGGERS | 存储 CalendarIntervalTrigger 和 DailyTimeIntervalTrigger 两种类型的触发器 |
QRTZ_TRIGGERS | 存储已配置的 Trigger 的信息 |
四、Quartz集成Spring
Spring 在 spring-context-support.jar 中直接提供了对 Quartz 的支持。
可以在配置文件中把 JobDetail、Trigger、Scheduler 定义成 Bean。
1、xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd"><!--Spring整合Quartz进行配置 步骤如下:1:定义工作任务的Job2:定义触发器Trigger,并将触发器与工作任务绑定3:定义调度器Scheduler,并将Trigger注册到Scheduler--><!-- 1:定义任务的bean ,这里使用JobDetailFactoryBean,也可以使用MethodInvokingJobDetailFactoryBean ,配置类似--><bean name="myJob1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"><!-- 指定job的名称 --><property name="name" value="my_job_1"/><!-- 指定job的分组 --><property name="group" value="my_group"/><!-- 指定具体的job类 --><property name="jobClass" value="com.test.quartz.MyJob1"/><!-- 必须设置为true,如果为false,当没有活动的触发器与之关联时会在调度器中会删除该任务 --><property name="durability" value="true"/><!-- 指定spring容器的key,如果不设定在job中的jobmap中是获取不到spring容器的 --><!--<property name="applicationContextJobDataKey" value="applicationContext"/>--></bean><bean name="myJob2" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"><!-- 指定job的名称 --><property name="name" value="my_job_2"/><!-- 指定job的分组 --><property name="group" value="my_group"/><!-- 指定具体的job类 --><property name="jobClass" value="com.test.quartz.MyJob2"/><!-- 必须设置为true,如果为false,当没有活动的触发器与之关联时会在调度器中会删除该任务 --><property name="durability" value="true"/><!-- 指定spring容器的key,如果不设定在job中的jobmap中是获取不到spring容器的 --><!--<property name="applicationContextJobDataKey" value="applicationContext"/>--></bean><!-- 2.1:定义触发器的bean,定义一个Simple的Trigger,一个触发器只能和一个任务进行绑定 --><!-- 一个Job可以绑定多个Trigger --><bean name="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"><!--指定Trigger的名称--><property name="name" value="my_trigger_1"/><!--指定Trigger的组别--><property name="group" value="my_group"/><!--指定Tirgger绑定的Job--><property name="jobDetail" ref="myJob1"/><!--指定Trigger的延迟时间 1s后运行--><property name="startDelay" value="1000"/><!--指定Trigger的重复间隔 5s--><property name="repeatInterval" value="5000"/><!--指定Trigger的重复次数--><property name="repeatCount" value="2"/></bean><!-- 2.2:定义触发器的bean,定义一个Cron的Trigger,一个触发器只能和一个任务进行绑定 --><bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"><!-- 指定Trigger的名称 --><property name="name" value="my_trigger_2"/><!-- 指定Trigger的名称 --><property name="group" value="my_group"/><!-- 指定Tirgger绑定的Job --><property name="jobDetail" ref="myJob2"/><!-- 指定Cron 的表达式 ,当前是每隔1s运行一次 --><property name="cronExpression" value="0/1 * * * * ?"/></bean><!-- 3.1 定义调度器,并将Trigger注册到调度器中。这种方式,任务只会存储到RAM。--><bean name="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"><property name="triggers"><list><ref bean="simpleTrigger"/><ref bean="cronTrigger"/></list></property></bean><!-- 3.2 持久化数据配置,需要添加quartz.properties。这种方式,任务会持久化到数据库。 -->
<!-- <bean name="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"><property name="applicationContextSchedulerContextKey" value="applicationContextKey"/><property name="configLocation" value="classpath:quartz.properties"/><property name="triggers"><list><ref bean="simpleTrigger"/><ref bean="cronTrigger"/></list></property></bean>--></beans>
public class MyJob1 implements Job{private Logger log = LoggerFactory.getLogger(this.getClass());public void execute(JobExecutionContext arg0) throws JobExecutionException {log.info("Job1执行:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) );}
}public class MyJob2 implements Job {private Logger log = LoggerFactory.getLogger(this.getClass());public void execute(JobExecutionContext arg0) throws JobExecutionException {log.info("Job2执行: "+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) );}
}
/*** 单元测试类*/
public class QuartzTest {private static Scheduler scheduler;public static void main(String[] args) throws SchedulerException {// 获取容器ApplicationContext ac = new ClassPathXmlApplicationContext("spring_quartz.xml");// 从容器中获取调度器scheduler = (StdScheduler) ac.getBean("scheduler");// 启动调度器scheduler.start();}}
2、注解配置
@Configuration
public class QuartzConfig {@Beanpublic JobDetail printTimeJobDetail(){return JobBuilder.newJob(MyJob1.class).withIdentity("testJob").usingJobData("test", "hello").storeDurably().build();}@Beanpublic Trigger printTimeJobTrigger() {CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");return TriggerBuilder.newTrigger().forJob(printTimeJobDetail()).withIdentity("quartzTaskService").withSchedule(cronScheduleBuilder).build();}
}
五、使用Quartz实现动态调度
Quartz实战:基于Quartz实现定时任务的动态调度,实现定时任务的增删改查
六、Quartz 集群部署
1、为什么需要集群?
1、防止单点故障,减少对业务的影响
2、减少节点的压力,例如在 10 点要触发 1000 个任务,如果有 10 个节点,则每个节点之需要执行 100 个任务
2、集群需要解决的问题
1、任务重跑,因为节点部署的内容是一样的,到 10 点的时候,每个节点都会执行相同的操作,引起数据混乱。比如跑批,绝对不能执行多次。
2、任务漏跑,假如任务是平均分配的,本来应该在某个节点上执行的任务,因为节点故障,一直没有得到执行。
3、水平集群需要注意时间同步问题
4、Quartz 使用的是随机的负载均衡算法,不能指定节点执行
在 Quartz 中,提供了一种简单的方式,基于数据库共享任务执行信息。也就是说,一个节点执行任务的时候,会操作数据库,其他的节点查询数据库,便可以感知到了。使用系统自带的 11 张表即可。
3、集群配置与验证
quartz.properties 配置。
四个配置:集群实例 ID、集群开关、数据库持久化、数据源信息
#如果使用集群,instanceId必须唯一,设置成AUTO即可
org.quartz.scheduler.instanceId = AUTO
#是否使用集群
org.quartz.jobStore.isClustered = true
# 数据库持久化+数据源 上面分析过了,此处略
注意先清空 quartz 所有表、改端口、两个任务频率改成一样
验证 1:先后启动 2 个节点,任务是否重跑
验证 2:停掉一个节点,任务是否漏跑
七、Quartz 调度原理与源码分析
Quartz 调度原理与源码分析
相关文章:

Quartz使用文档,使用Quartz实现动态任务,Spring集成Quartz,Quartz集群部署,Quartz源码分析
文章目录 一、Quartz 基本介绍二、Quartz Java 编程1、文档2、引入依赖3、入门案例4、默认配置文件 三、Quartz 重要组件1、Quartz架构体系2、JobDetail3、Trigger(1)代码实例(2)SimpleTrigger(3)CalendarI…...

Go -- 测试 and 项目实战
没有后端基础,学起来真是费劲,所以打算速刷一下,代码跟着敲一遍,有个印象,大项目肯定也做不了了,先把该学的学了,有空就跟点单体项目,还有该看的书.... 目录 🍌单元测试…...
GitHub基本使用
GitHub搜索 直接搜索 直接搜索关键字 明确搜索仓库标题 语法:in:name [关键词]展示:比如我们想在GitHub仓库中标题中搜索带有SpringBoot关键词的,我们可以样搜: in:name SpringBoot 明确搜索描述 语法:in:description [关键词]展…...

微信小程序生成带参数的二维码base64转png显示
getQRCode() {var that this;wx.request({url: http://localhost:8080/getQRCode?ID 13,header: {content-type: application/json},method: POST,responseType: arraybuffer,//将原本按文本解析修改为arraybuffersuccess(res) {that.setData({getQRCode: wx.arrayBufferToB…...
量子计算机:下一代计算技术的奇点
介绍 量子计算机是一种基于量子力学原理的全新计算技术,它利用量子比特的特性进行计算,具有破解当前经典计算机难以解决问题的潜力。在过去几十年里,量子计算机一直是计算机科学领域的一个热门话题。本篇博客将深入探讨量子计算机的基本原理…...

【ChatGPT】ChatGPT是如何训练得到的?
前言 ChatGPT是一种基于语言模型的聊天机器人,它使用了GPT(Generative Pre-trained Transformer)的深度学习架构来生成与用户的对话。GPT是一种使用Transformer编码器和解码器的预训练模型,它已被广泛用于生成自然语言文本的各种…...
Docker设置代理、Linux系统设置代理
使用方式 新建或修改~/.docker/config.json文件,设置可用的代理地址。 {"proxies": {"default": {"httpProxy": "http://192.168.0.32:1080","httpsProxy": "http://192.168.0.32:1080","noPro…...
C# 进程
C# 进程 进程的命名空间是: using System.Diagnostics;1.获取当前计算机正在运行所有的进程 Process[] processes Process.GetProcesses(); for (int i 0; i < processes.Length; i) {Console.WriteLine(processes[i]); } Console.ReadKey();2.通过进程打开…...

SQL注入之sqlmap
SQL注入之sqlmap 6.1 SQL注入之sqlmap安装 sqlmap简介: sqlmap是一个自动化的SQL注入工具,其主要功能是扫描,发现并利用给定的URL的SQL注入漏洞,目前支持的数据库是MS-SQL,MYSQL,ORACLE和POSTGRESQL。SQLMAP采用四种独特的SQL注…...
Flutter 命名路由
我们可以通过创建一个新的Route,使用Navigator来导航到一个新的页面,但是如果在应用中很多地方都需要导航到同一个页面(比如在开发中,首页、推荐、分类页都可能会跳到详情页),那么就会存在很多重复的代码。…...

Stephen Wolfram:神经网络
Neural Nets 神经网络 OK, so how do our typical models for tasks like image recognition actually work? The most popular—and successful—current approach uses neural nets. Invented—in a form remarkably close to their use today—in the 1940s, neural nets …...

RBF神经网络原理和matlab实现
1.案例背景 1.1 RBF神经网络概述 径向基函数(Radical Basis Function,RBF)是多维空间插值的传统技术,由Powell于1985年提出。1988年, Broomhead和 Lowe根据生物神经元具有局部响应这一特点,将 RBF引入神经网络设计中,产生了RBF神经网络。1989 年,Jackson论证了…...

Nacos 抽取公共配置
文章目录 创建一个公共配置文件其他配置文件引用springboot配置文件 创建一个公共配置文件 其他配置文件引用 ${变量} springboot配置文件 spring:cloud:nacos:discovery:server-addr: current.ip:8848namespace: word_register_proconfig:server-addr: current.ip:8848auto-r…...
Promise、Async/Await 详解
一、什么是Promise Promise是抽象异步处理对象以及对其进行各种操作的组件。Promise本身是同步的立即执行函数解决异步回调的问题, 当调用 resolve 或 reject 回调函数进行处理的时候, 是异步操作, 会先执行.then/catch等,当主栈完成后&#…...

PoseiSwap:基于 Nautilus Chain ,构建全新价值体系
在 DeFi Summer 后,以太坊自身的弊端不断凸显,而以 Layer2 的方式为其扩容成为了行业很长一段时间的叙事方向之一。虽然以太坊已经顺利的从 PoW 的 1.0 迈向了 PoS 的 2.0 时代,但以太坊创始人 Vitalik Buterin 表示, Layer2 未来…...

uC-OS2 V2.93 STM32L476 移植:串口打印篇
前言 前几篇已经 通过 STM32CubeMX 搭建了 NUCLEO-L476RG 的 STM32L476RG 的 裸机工程,下载了 uC-OS2 V2.93 的源码,并把 uC-OS2 的源文件加入 Keil MDK5 工程,通过适配 Systick 系统定时器与 PendSV 实现任务调度,初步让 uC-OS2 …...
代码随想录算法训练营第四十六天| 139.单词拆分 背包问题总结
代码随想录算法训练营第四十六天| 139.单词拆分 背包问题总结 一、力扣139.单词拆分 题目链接: 思路:确定dp数组,dp[i]为true表示从0到i切分的字串都在字典中出现过。 确定递推公式,dp[i] 为true要求 s[j, i] 在字典中出现&…...

【机器学习】西瓜书习题3.3Python编程实现对数几率回归
参考代码 结合自己的理解,添加注释。 代码 导入相关的库 import numpy as np import pandas as pd import matplotlib from matplotlib import pyplot as plt from sklearn import linear_model导入数据,进行数据处理和特征工程 # 1.数据处理&#x…...

Blazor前后端框架Known-V1.2.9
V1.2.9 Known是基于C#和Blazor开发的前后端分离快速开发框架,开箱即用,跨平台,一处代码,多处运行。 Gitee: https://gitee.com/known/KnownGithub:https://github.com/known/Known 概述 基于C#和Blazor…...
【3D捏脸功能实现】
文章目录 一、技术方案介绍二、技术核心三、底层技术实现选型进行模型建模编写逻辑代码 四、功能落地五、总结 一、技术方案介绍 3D捏脸功能是一种利用3D技术实现用户自定义头像的功能。通常实现这种功能需要以下技术: 3D建模技术。通过3D建模技术可以创建一个可以…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...