Quartz框架是一个轻量级的任务调度框架,它提供了许多内置的功能,包括:支持作业的调度、集群调度、持久化、任务持久化、任务依赖、优先级、并发控制、失败重试等。同时也支持自定义作业类型和触发器类型。
下面是Quartz框架学习的详细笔记:
Quartz框架概述
Quartz框架是一个用于Java应用程序的Open Source任务调度框架。其主要优点是与各种应用程序集成简单,提供了强大的调度功能。它是一种纯Java的框架,可以在任何Java应用程序中使用。
Quartz框架的核心概念
Quartz框架的核心概念包括:
1、Job:表示一个需要执行的任务。在Quartz中,所有的任务都是实现了Job接口的类。
2、JobDetail (工作细节)表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
3、Trigger 触发器,代表一个调度参数的配置,什么时候去调。
4.Scheduler (日程)调度器,代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
Scheduler 调度器 》 Trigger 触发器》 JobDetail 工作策略 》Job 工作
Quartz框架的工作流程
Quartz框架的工作流程如下:
- 首先,定义需要执行的任务(Job),并指定任务的执行规则(Trigger)。
- 其次,将任务和触发器注册到调度器中。
- 最后,调度器按照指定的条件和规则执行任务。
Quartz框架的使用
Quartz框架的使用步骤如下:
- 引入依赖:首先需要在项目中引入Quartz框架的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
- 编写任务类:定义需要执行的任务类,实现Job接口。
package com.xxgc.demo.job;
/*
*
* @Author:何包蛋
* @Date: 2023/9/27-09-27-14:23
* Job工作对象
*/
import com.xxgc.demo.utils.SpeechSynthesisUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
@Slf4j
public class SendEmailToGirlFriendJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//具体执行的代码
log.info("条件满足我被执行了");
SpeechSynthesisUtil synthesisUtil = new SpeechSynthesisUtil();
synthesisUtil.speak("今天早上给你和你男朋友买了早餐,你给我说了谢谢,我就知道你心里有我。");
}
}
- 编写触发器类:定义任务的调度规则,实现Trigger接口。
public class HelloScheduler {
public static void main(String[] args) throws SchedulerException {
//1.创建一个jobDetail的实例,将该实例与HelloJob Class绑定
JobDetail jobDetail = JobBuilder
.newJob(HelloJob.class) //定义Job类为HelloJob类,真正的执行逻辑所在
.withIdentity("myJob", "group1") //定义name 和 group
.usingJobData("message","hello myJob1") //加入属性到jobDataMap
.usingJobData("FloatJobValue",8.88f) //加入属性到jobDataMap
.build();
//2.创建一个Trigger触发器的实例,定义该job立即执行,并且每2秒执行一次,一直执行
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
.build();
//3.创建schedule实例
StdSchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
scheduler.start(); //启动
scheduler.scheduleJob(jobDetail,trigger); // jobDetail和trigger加入调度
}
}
- 注册任务和触发器:将任务和触发器注册到调度器中。
@Schema(name = "任务调度VO",description = "任务调度VO")
@Data
public class QuartzTaskVO {
@NotNull(message = "Cron表达式不能为空")
@Schema(description = "Cron表达式")
private String cron;
@NotNull(message = "触发器名称不能为空")
@Schema(description = "触发器名称")
private String triggerName;
@NotNull(message = "触发器分组不能为空")
@Schema(description = "触发器分组")
private String triggerGroup;
@NotNull(message = "任务名称不能为空")
@Schema(description = "任务名称")
private String jobName;
@NotNull(message = "任务分组不能为空")
@Schema(description = "任务分组")
private String jobGroup;
@NotNull(message = "执行任务对象地址不能为空")
@Schema(description = "执行任务对象地址")
private String jobClass;
/*
*
* @Author:H
* @Date: 2023/9/27-09-27-16:07
* 任务调度接口
*/
@Slf4j
@Tag(name = "任务调度")
@RestController
@RequestMapping("/quartz")
public class QuartzController {
@Operation(summary = "添加任务",description = "任务")
@PostMapping("/addTask")
public Result addTask(@RequestBody @Valid QuartzTaskVO qtvo) throws SchedulerException, ClassNotFoundException {
SchedulerUtil.create()
.withTrigger(qtvo.getTriggerName(),qtvo.getTriggerGroup(),qtvo.getCron())
.withJob(qtvo.getJobName(),qtvo.getJobGroup(), (Class<? extends Job>) Class.forName(qtvo.getJobClass()))
.schedule();
return Result.ok("添加任务成功");
}
}
- 启动调度器:调用调度器的start()方法启动调度器,开始执行任务。
- 关闭调度器:调用调度器的shutdown()方法关闭调度器。
@Test
void quartzTest() throws SchedulerException, InterruptedException {
//调度器 - 从工厂获取
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
//触发器 - 触发条件(什么时候执行)
String cron = "0 13 15 * * ?";
Trigger trigger = TriggerBuilder.newTrigger()
//触发器名字和分类
.withIdentity("sendEmail","girlFriend")
//给触发器加条件 用cron表达式加条件
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
//条件满足立即执行
.startNow()
.build();
//工作策略 - 指定由哪个类来完成
JobDetail jobDetail = JobBuilder.newJob(SendEmailToGirlFriendJob.class)
.withIdentity("sendEmailJob","girlFriendJobGroup")
.build();
//把触发器和工作策略绑定,并交由调度器进行管理
scheduler.scheduleJob(jobDetail,trigger);
//启动调度器
scheduler.start();
//卡在这1个小时
Thread.sleep(1000 * 60 * 60);
}
Quartz框架的触发器类型
Quartz框架支持多种类型的触发器,包括:
- SimpleTrigger:用于在指定时间触发一次任务。
- CronTrigger:用于按照指定的时间规则触发任务。
- CalendarIntervalTrigger:用于按照指定的时间间隔触发任务。
- DailyTimeIntervalTrigger:用于按照指定的时间间隔和时间段触发任务。
Quartz框架的任务类型
Quartz框架支持多种类型的任务,包括:
- SimpleJob:简单任务。
- StatefulJob:有状态的任务,需要等待任务完成后才能执行下一个任务。
- JobDetail:可以设置任务的参数和描述信息等。
Quartz框架的集群调度
Quartz框架支持集群调度,可以在多个节点上进行任务的调度和执行。在集群中,一个任务只会在其中一个节点上执行。
Quartz提供了一种称为集群调度的机制,该机制允许多个节点协同工作,以确保在任何时候都只有一个节点在运行作业。
为实现集群调度,每个节点都必须连接到同一个数据库,并且调度器实例必须具有唯一的标识符。
在集群环境中,所有节点均能读取和更新数据库中的调度状态。当调度器启动时,它将尝试获取所有未锁定的触发器,并将它们安排到本地节点上执行。
如果另一个节点已经为某个作业定时触发器,则该作业将在该节点上执行。当该节点完成作业时,它会将作业状态更新到数据库,通知其他节点它已经完成了该作业。
此外,Quartz默认使用基于数据库的锁定机制来确保同一时刻只有一个节点在运行作业。如果一个节点无法获得锁定,则它将等待直到能够获得锁定。
- 集群调度的优缺点
优点:
- 确保在任何时候只有一个节点在运行作业,避免了重复运行作业的情况。
- 允许快速水平扩展,增加节点可以提高系统的整体性能。
缺点:
- 集群调度需要一个共享的数据库,这可能会成为系统的瓶颈。
- 必须考虑使用有效的锁定机制和数据库结构来确保系统的稳定性和可靠性。
Quartz框架的持久化
Quartz框架支持任务的持久化,可以将任务的状态存储在数据库中,保证任务信息不会丢失。
-
使用JDBC持久化:Quartz支持使用JDBC进行持久化。在使用JDBC持久化时,需要配置Quartz的数据源和相应的数据库表。在启动Quartz时,它将自动创建任何缺少的表。如果您选择使用JDBC持久化,还需要为Quartz配置一个特殊的数据源,该数据源必须提供对Quartz所需的所有表的访问权限。
-
使用Terracotta持久化:Terracotta是一种基于内存的分布式缓存解决方案,可用于将Quartz的任务和触发器持久保存在内存中。使用Terracotta持久化可以提高Quartz的性能和可伸缩性,并且不需要任何专用的数据库表。
-
权衡:选择哪种持久化机制取决于您的应用程序的特定要求。如果您的应用程序需要持久化任务和触发器,但不需要性能和可伸缩性,则使用JDBC持久化可能是最好的选择。如果您需要更好的性能和可伸缩性,则Terracotta持久化可能是更好的选择。
-
状态对象:在使用任何类型的持久化时,您必须考虑如何处理您的任务和触发器的状态对象。当Quartz重新启动时,它必须能够重新创建这些状态对象。您可以选择在状态对象中嵌入序列化对象,也可以将状态保存在数据库或分布式缓存中。
Quartz框架的错误处理和重试机制
Quartz框架支持错误处理和重试机制,可以在任务执行失败后进行错误处理和重试,保证任务的正确执行。
cron表达式:
-
*
:代表匹配所有可能的值。 -
-
:用于指定一个范围,例如1-5
表示从1到5。 -
/
:用于指定步长,例如0/15
表示每15分钟执行一次。 -
,
:用于指定多个值,例如1,3,5
表示在1、3和5时执行。 -
?
:在某些字段中可用,例如在星期字段中表示不指定值。
Quartz工具类封装:
/*
*
* @Author:何包蛋
* @Date: 2023/9/27-09-27-16:02
* 任务调度工具类
*/
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class SchedulerUtil {
private Scheduler scheduler;
private Trigger trigger;
private JobDetail jobDetail;
private SchedulerUtil(Scheduler scheduler) {
this.scheduler = scheduler;
}
public static SchedulerUtil create() throws SchedulerException {
return new SchedulerUtil(StdSchedulerFactory.getDefaultScheduler());
}
public SchedulerUtil withTrigger(String name, String group, String cronExpression) {
this.trigger = TriggerBuilder.newTrigger()
.withIdentity(name, group)
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.startNow()
.build();
return this;
}
public SchedulerUtil withJob(String name, String group, Class<? extends Job> jobClass) {
this.jobDetail = JobBuilder.newJob(jobClass)
.withIdentity(name, group)
.build();
return this;
}
public void schedule() throws SchedulerException {
if (trigger == null || jobDetail == null) {
throw new IllegalStateException("Trigger and JobDetail must be set before scheduling.");
}
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}