任务调度管理——Quartz入门
📕作者简介:战斧,从事金融IT行业,有着多年一线开发、架构经验;爱好广泛,乐于分享,致力于创作更多高质量内容
📗本文收录于 Quartz专栏 ,有需要者,可直接订阅专栏实时获取更新
📘高质量专栏 云原生、RabbitMQ、Spring全家桶、 GIT 等仍在更新,欢迎指导
📙Zookeeper Redis kafka docker netty等诸多框架,以及架构与分布式专题即将上线,敬请期待
一、Quartz 介绍
1. Quartz 是什么
相信同学们在项目开发中经常遇到诸如定时任务这类要求,常见的比如
- 每隔5分钟查询账户金额
- 每天晚上八点自动触发垃圾清理脚本
- 周一到周五自动生成并发送报表邮件
这个时候我们就需要利用任务调度框架来负责,比如我们今天要说的Quartz
Quartz是一个功能强大的分布式任务调度框架,被广泛应用于各种复杂的任务调度场景中。其具有高可靠性、高扩展性和灵活性等特点,可以帮助开发人员轻松实现任务的管理和调度。
2. 与@scheduled注解比较
说到调度,Spring自己也提供了@scheduled的注解来实现调度。 那与Quartz相比,两者有什么异同,该怎么选呢?
特点 | Quartz | @Scheduled |
---|---|---|
任务调度 | 支持灵活的任务调度功能,可以按照固定的时间间隔、指定时间点或者某种条件进行调度任务 | 支持定时任务调度,可以按照固定的时间间隔或者指定时间点调度任务 |
分布式 | 支持分布式任务调度,可以在多个节点上同时运行任务 | 不支持分布式任务调度,只能在单个节点上运行任务 |
可扩展性 | 支持插件机制,可以通过自定义插件来扩展功能 | 不支持插件机制,无法进行功能扩展 |
监控和管理 | 提供了一套完善的监控和管理工具,可以监控任务的执行情况,并提供了Web界面进行管理 | 不提供监控和管理工具,需要通过其他方式进行监控和管理 |
容错性 | 具备容错机制,当任务执行失败或者节点故障时,可以进行任务重试或者切换到其他可用节点上继续执行 | 不具备容错机制,任务执行失败后无法进行重试或者切换 |
并发性 | 支持并发执行任务,可以同时执行多个任务 | 只能串行执行任务,无法同时执行多个任务 |
通俗来说,就是一个简单易用但功能少,一个复杂但功能强大。像一些要求比较复杂的场景,比如定时邮件任务,由集群里某一台服务器来执行就行了,不需要每台服务器都发一遍邮件,这时候@schedual 就捉襟见肘了,而使用 Quartz 其自带的分布式控制就能轻松实现
二、Quartz 的用法
1. Quartz 的结构设计
我们先来看下Quartz 的核心结构设计:
其中涉及到了三个部分:调度器、触发器、作业
-
调度器(Scheduler)
调度器是 Quartz 的核心组件,负责创建、启动和调度任务。它可以与数据库进行交互,通过查询和更新任务信息来实现任务的调度和管理。 -
触发器(Trigger)
触发器用于指定作业的调度规则,决定何时执行作业。Quartz 提供了多种类型的触发器,如 SimpleTrigger、CronTrigger 等。触发器可以设置触发时间、重复次数和间隔等属性,可以与作业一起关联,形成调度关系。 -
作业(Job)
作业是需要被调度的任务,实现了 Job 接口。作业可以是简单的 Java 类,也可以是通过实现 Job 接口的类。Quartz 可以调度和执行不同类型的作业,如无状态作业和有状态作业。
简单的说就是job里记录着”我们要做的事情“;Trigger里放着“做这些事的时间”;而“协调众多任务”就得靠Scheduler了,一个需要注意的细节就是,一个作业,可以被多个Trigger触发,就比如发工资(job)在每个月(trigger)都会被执行,但在年末(trigger)也可能会触发(年终奖)
2. Quartz 使用demo
在使用之前,我们先要进行引用Quartz框架,如果你本身是spring-boot 2 以上的项目,直接在pom中加如下引用即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
单独引用的话,可以使用这个
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
比如我现在想实现上面提到过的,在每周一到周五的早上八点发送邮件的功能。我们可以这么写,先把我们的job写出来
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class SendEmailJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 在这里编写发送邮件的逻辑
System.out.println("发送邮件");
// 这里可以使用邮件发送的库,例如JavaMail等
}
}
然后开始写实现的Demo :
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.text.ParseException;
import java.util.Date;
public class SchedulerDemo {
public static void main(String[] args) throws SchedulerException, ParseException {
// 创建调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 创建任务
JobDetail job = JobBuilder.newJob(SendEmailJob.class)
.withIdentity("sendEmailJob", "group1")
.build();
// 创建触发器,设置触发时间为每周一到周五的早上八点
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("sendEmailTrigger", "group1")
.startAt(DateBuilder.nextGivenSecondDate(null, 1))
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 8 ? * MON-FRI"))
.build();
// 将任务和触发器添加到调度器中
scheduler.scheduleJob(job, trigger);
// 启动调度器
scheduler.start();
}
}
当然上述是一个Demo,真正在项目中使用,像 调度器
和 触发器
都是通过Bean注册进容器中的,如下:
@Bean
public CronTriggerFactoryBean myJobTrigger() {
CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
cronTriggerFactoryBean.setJobDetail(myJobDetail());
cronTriggerFactoryBean.setCronExpression("0 0 8 ? * MON-FRI"); // 每5秒执行一次
return cronTriggerFactoryBean;
}
@Bean
public JobDetailFactoryBean myJobDetail() {
JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
jobDetailFactoryBean.setJobClass(SendEmailJob.class);
return jobDetailFactoryBean;
}
此时的调度器启用,则是放在启动类加上 @EnableScheduling
注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3. CRON表达式
需要注意的是,Trigger 的触发条件是使用CRON表达式的,CRON
表达式是一种用于指定定时任务执行时间的字符串格式。它由7或6个字段(年份字段可省略)组成,形如* * * * * * ,每个字段用空格分隔,依次表示分钟、小时、日期、月份、星期几和年份。
0 15 10 * * ? 2024
秒 分 时 日期 月份 星期 年份
域 | 语法 | 用法 |
---|---|---|
秒 | 0-59 | 可以指定具体的秒数 |
分钟 | 0-59 | 可以指定具体的分钟数,例如,5表示每小时的第5分钟执行一次。也可以使用通配符表示每分钟都执行。还可以使用逗号,分隔多个值,例如5,10,15表示第5、10和15分钟执行。还可以使用连字符-表示一个范围,例如10-30表示从第10分钟到第30分钟之间的每分钟都执行。还可以使用斜杠/表示间隔执行,例如/5表示每5分钟执行一次。 |
小时 | 0-23 | 类似于分钟域,可以指定具体的小时数,使用通配符*、逗号,、连字符-和斜杠/进行设置。 |
日期 | 1-31 | 可以指定具体的日期,使用通配符*、逗号,、连字符-和斜杠/进行设置。还可以使用特殊字符L表示每个月的最后一天,例如L表示每个月的最后一天执行。还可以在日期字段之前加上W表示最接近指定日期的工作日(周一至周五)执行任务,例如15W表示最接近15号的工作日执行任务。还可以使用?表示该字段不指定具体日期。 |
月份 | 1-12 或 JAN-DEC | 类似于日期域,可以指定具体的月份,使用通配符*、逗号,、连字符-和斜杠/进行设置。 |
星期几 | 0-7 或 SUN-SAT | 可以指定具体的星期几,0和7都表示周日,1-6分别表示周一至周六,使用通配符*、逗号,、连字符-和斜杠/进行设置。还可以使用特殊字符L表示每个月的最后一个工作日执行任务。还可以使用#和数字表示每个月的第几个星期几执行任务,例如3#1表示每个月的第一个星期二执行任务。还可以使用?表示该字段不指定具体星期几。 |
年份 | 空或 1970-2099 | 类似于其它域,可以指定具体的年份,使用通配符*、逗号,、连字符-和斜杠/进行设置。 |
上面的域有些是支持特殊字符的,用来实现更复杂的时间控制:
-
星号 (*)
表示匹配任意值。可以在秒、分钟、小时、日期、月份和星期中使用。例如,在分钟域上使用 * 表示每分钟都执行。 -
问号 (?)
在日期和星期中用于替代具体的值,表示不确定的值。例如,在星期域上使用"?"表示不考虑星期,可以与日期域一起使用。 -
斜线 (/)
用于指定范围内的间隔值。例如,在小时域上使用"*/2"表示每隔两小时执行一次。 -
逗号 (,)
用于指定多个值。例如,在小时域上使用"1,5,10"表示1点、5点和10点都会执行。 -
减号 (-)
用于指定一个范围内的连续值。例如,在日期域上使用"1-5"表示1号到5号都会执行。 -
L
用于表示最后的值。可以在日期和星期域中使用。例如,在日期域上使用"L"表示最后一天。 -
W
用于指定最近的工作日。可以在日期域中使用。例如,"15W"表示最近的工作日到15号。 -
#
用于指定每月的第几个星期几。可以在星期域中使用。例如,在星期域上使用"3#1"表示每月的第一个星期二。
我们用一点例子来解释上面这些规则的用法:
表达式 | 含义 |
---|---|
0 0/2 * * * ? | 每2分钟 执行任务 |
0 0/30 9-17 * * ? | 朝九晚五工作时间内每半小时 |
0 15 10 L * ? | 每月最后一日的上午10:15执行 |
0 15 10 ? * 6#3 | 每月的第三个星期五上午10:15执行 |
0 10,44 14 ? 3 WED | 每年三月的星期三的下午2:10和2:44执行 |
0 0 10,14 ? * 2-6#1 | 每个月的第一个星期一至星期五的上午10点和下午2点执行 |
三、 总结
上面我们简单介绍了Quartz是什么以及其基本用法,对于初学者来说,会用就是迈开了第一步,后续我们会详细展开对它的学习,包括理解它的原理与高级用法。