在项目中开发定时任务应该一种比较常见的需求,在 Java 中开发定时任务主要有三种解决方案:一是使用JDK 自带的 Timer,二是使用 Spring Task,三是使用第三方组件 Quartz。
建议:
单体项目架构使用Spring Task(支持注解和配置文件两种形 )。
分布式项目架构使用Quartz
/**
* 基于jdk的任务调度
*/
public class JdkTaskDemo {
public static void main(String[] args) {
//创建定时类
Timer timer = new Timer();
//创建任务类
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("定时任务执行了......"+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
};
//执行定时任务
timer.schedule(task,new Date(),2000);
}
}
【1】 导入spring-boot-starter-web即可,不需导入任何其他依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>top.psjj</groupId>
<artifactId>task-study</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>task-study</name>
<description>task-study</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
【2】编写启动类,打开任务调度注解
package top.psjj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class TaskStudyApplication {
public static void main(String[] args) {
SpringApplication.run(TaskStudyApplication.class, args);
}
}
【3】编写任务类测试
package top.psjj.task;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* @Auther: 胖叔讲java
* @Date: 2024/1/2 - 01 - 02 - 20:06
* @Decsription: top.psjj.task
* @version: 1.0
*/
@Component
public class SpringTask {
@Scheduled(cron = "*/1 * * * * *")
public void task1() throws InterruptedException {
System.out.println(Thread.currentThread().getName()+":task1--->"+ LocalDateTime.now());
}
}
【4】运行结果
Spring-task执行的任务是基于单线程执行的。由此得出两个结论:
Spring-task 执行任务按照单线程执行并合理执行,不会因为第一个执行任务时间过长而执行第二个。
Spring-task是单线程的处理任务能力有限,不建议处理分布式架构的任务调度。
?关于 cronExpression 表达式有至少 6 个(也可能是 7 个)由空格分隔的时间元素。从左至右,这些元素的定义如下:
- 秒? ? ? ? 0-59 , - * /
- 分? ? ? ? 0-59 , - * /
- 小时? ? ?0-23 , - * /
- 日? ? ? ? ?1-31 , - * ? / L W C
- 月? ? ? ? ?1-12 or JAN-DEC , - * /
- 周几? ? ? 1-7 or SUN-SAT , - * ? / L C #
- 年(可选字段) empty, 1970-2099 , - * /
0 0 10,14,16 * * ?
每天上午 10 点,下午 2 点和下午 4 点
0 0,15,30,45 * 1-10 * ?
每月前 10 天每隔 15 分钟
30 0 0 1 1 ? 2012
在 2012 年 1 月 1 日午夜过 30 秒时
?可用值详细分析如下:
"*" —— 字符可以用于所有字段,在"分"字段中设为"*",表示"每一分钟"的含义。
"?" —— 字符可以用在"日"和"周几"字段,它用来指定"不明确的值"。
这在你需要指定这两个字段中的某一个值而不是另外一个的时候会被用到。在后面的例子中可以看到
其含义。
"-" —— 字符被用来指定一个值的范。
比如在"小时"字段中设为"10-12",表示"10 点到 12 点"。
"," —— 字符指定数个值。
比如在"周几"字段中设为"MON,WED,FRI",表示"the days Monday, Wednesday, and Friday"。
"/" —— 字符用来指定一个值的的增加幅度。
比如在"秒"字段中设置为"0/15"表示"第 0, 15, 30,和 45 秒"。
而"5/15"则表示"第 5, 20, 35,和 50"。
在'/'前加"*"字符相当于指定从 0 秒开始。每个字段都有一系列可以开始或结束的数值。
对于"秒"和"分"字段来说,其数值范围为 0 到 59。
对于"小时"字段来说其为 0 到 23,对于“日”字段来说为 0 到 31。
而对于"月"字段来说为 1 到 12。
"/"字段仅仅只是帮助你在允许的数值范围内从开始"第 n"的值。
"L" —— 字符可用在"日"和"周几"这两个字段。它是"last"的缩写,但是在这两个字段中有不同的含义。
"日"字段中的"L"表示"一个月的最后一天",对于一月就是 31 号,对于二月就是 28 号(非闰年)。
"周几"字段中,它简单的表示"7" or "SAT"。
但是如果在"周几"字段中使用时跟在某个数字之后,它表示"该月最后一个星期×"。
比如"6L"表示"该月最后一个周五"。
当使用"L"选项时,指定确定的列表或者范围非常重要,否则你会被结果搞糊涂的。
"W" —— 可用于"日"字段。用来指定历给定日期最近的工作日(周一到周五)。
比如将"日"字段设为"15W",意为: "离该月 15 号最近的工作日"。
因此如果 15 号为周六,触发器会在 14 号即周五调用。
如果 15 号为周日,触发器会在 16 号也就是周一触发。如果 15 号为周二,那么当天就会触发。
如果"日"字段设为"1W",而一号是周六,于下周一即当月的 3 号触发,不会越过当月的值的范围边界。
"W"字符只能用于"日"字段的值为单独的一天而不是一系列值的时候。
"L"和"W"可以组合用于“日”字段表示为'LW',意为"该月最后一个工作日"。
"#" —— 字符可用于"周几"字段。该字符表示"该月第几个周×"。
比如"6#3"表示该月第三个周五( 6 表示周五,而"#3"该月第三个)。
再比如: "2#1" 表示该月第一个周一,而"4#5" 该月第五个周三。
注意如果你指定"#5"该月没有第五个"周×",该月是不会触发的。
"C" —— 字符可用于"日"和"周几"字段,它是"calendar"的缩写。
它表示为基于相关的日历所计算出的值(如果有)。如果没有关联的日历,那它等同于包含全部日历。
"日"字段值为"5C",表示"日历中的第一天或者 5 号以后"。
"周几"字段值为"1C",则表示"日历中的第一天或者周日以后"。
对于"月份"字段和"周几"字段来说合法的字符都不是大小写敏感的。
例子?
"0 0 12 * * ?" 每天中午十二点触发
"0 15 10 ? * *" 每天早上 10:15 触发
"0 15 10 * * ?" 每天早上 10:15 触发
"0 15 10 * * ? *" 每天早上 10:15 触发
"0 15 10 * * ? 2005" 2005 年的每天早上 10:15 触发
"0 * 14 * * ?" 每天从下午 2 点开始到 2 点 59 分每分钟一次触发
"0 0/5 14 * * ?" 每天从下午 2 点开始到 2:55 分结束每 5 分钟一次触发
"0 0/5 14,18 * * ?" 每天的下午 2 点至 2:55 和 6 点至 6 点 55 分两个时间段内每 5分钟一次触发
"0 0-5 14 * * ?" 每天 14:00 至 14:05 每分钟一次触发
"0 10,44 14 ? 3 WED" 三月的每周三的 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
实际开发在线文档自动生成
Quartz是
OpenSymphony
开源组织在Job scheduling
领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer
。但是相较于Timer, Quartz增加了很多功能:
持久性作业 - 就是保持调度定时的状态;
作业管理 - 对调度作业进行有效的管理;
官方文档:
Quartz 的核心类有以下三部分:
- 任务类Job:需要实现的任务类,实现execute()方法,执行后完成任务。
- 触发器Trigger:包括
SimpleTrigger
和CronTrigger
。- 调度器Scheduker:任务调度器,负责基于
Trigger
触发器,来执行 Job任务。
1)创建springboot工程,导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>top.psjj</groupId>
<artifactId>quartz-study</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>quartz-study</name>
<description>quartz-study</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2)新建任务类
package top.psjj.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
* @Auther: 胖叔讲java
* @Date: 2024/1/3 - 01 - 03 - 17:49
* @Decsription: top.psjj.job
* @version: 1.0
*/
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("任务被执行了");
}
}
3)创建调度器、jobDetail 实例、trigger 实例、执行
public class QuartzTest {
public static void main(String[] args) throws SchedulerException {
//1.创建任务调度器
SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
//2.创建JobDetail实例,并与MyJob类绑定
JobDetail job = JobBuilder.newJob(MyJob.class)
//指定任务名,组名
.withIdentity("job1","group1")
.build();
//3.构建Trigger实例,每隔3s执行一次
Trigger trigger = TriggerBuilder.newTrigger()
//指定触发器名字,组名
.withIdentity("trigger1","group1")
//从现在触发
.startNow()
//触发规则3s触发一次
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever())
.build();
//4.执行 开启任务调度器
scheduler.scheduleJob(job,trigger);
System.out.println(System.currentTimeMillis());
scheduler.start();
}
}
JobDetail 的作用是绑定 Job,是一个任务实例,它为 Job 添加了许多扩展参数。
主要字段 | 含义 |
---|---|
name | 任务名称 |
group | 任务分组,默认分组DEFAULT |
jobClass | 要执行的Job实现类 |
jobDataMap | 任务参数信息,JobDetail、Trigger都可以使用JobDataMap来设置一些参数或者信息 |
每次Scheduler调度执行一个Job的时候,首先会拿到对应的Job,然后创建该Job实例,再去执行Job中的
execute()
的内容,任务执行结束后,关联的Job对象实例会被释放,且会被JVM GC清除 ?
?为什么设计成JobDetail + Job,不直接使用Job?
- JobDetail 定义的是任务数据,而真正的执行逻辑是在Job中。
- 因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。
- JobDetail & Job 方式,Sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以 规避并发访问 的问题。
比较简单的一类触发器,用它能实现很多基础的应用。使用它的主要场景包括:
在指定时间段内,执行一次任务。最基础的 Trigger 不设置循环,设置开始时间。
在指定时间段内,循环执行任务。在 1 基础上加上循环间隔。可以指定永远循环、运行指定次数。
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger2","group1")
.startNow()
.withSchedule(
//使用简单触发器
SimpleScheduleBuilder.simpleSchedule().
//3s间隔执行
withIntervalInSeconds(3).
//始终执行
repeatForever())
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger2","group1")
.startNow()
.withSchedule(
//使用简单触发器
SimpleScheduleBuilder.simpleSchedule().
//3s间隔执行
withIntervalInSeconds(3).
//执行6次 count+1
withRepeatCount(5))
.build();
CronTrigger 是基于日历的任务调度器,在实际应用中更加常用。,但是知识点与SimpleTrigger一样,只是可以通过表达式来设置时间而已。
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger2","group1")
.startNow()
.withSchedule(
//使用日历触发器
CronScheduleBuilder.cronSchedule("0/1 * * * * ? "))
.build();
1)添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>top.psjj</groupId>
<artifactId>quartz-study</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>quartz-study</name>
<description>quartz-study</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2)编写application.yml配置文件
server:
port: 80
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:3306/quartz?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
# 定时配置
quartz:
# 相关属性配置
properties:
org:
quartz:
# 数据源
dataSource:
globalJobDataSource:
# URL必须大写
URL: jdbc:mysql://127.0.0.1:3306/quartz?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
driver: com.mysql.cj.jdbc.Driver
maxConnections: 5
username: root
password: 123456
# 必须指定数据源类型
provider: hikaricp
scheduler:
instanceName: globalScheduler
# 实例id
instanceId: AUTO
type: com.alibaba.druid.pool.DruidDataSource
jobStore:
# 数据源
dataSource: globalJobDataSource
# JobStoreTX将用于独立环境,提交和回滚都将由这个类处理
class: org.quartz.impl.jdbcjobstore.JobStoreTX
# 驱动配置
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 表前缀
tablePrefix: QRTZ_
# 失效阈值(只有配置了这个时间,超时策略根据这个时间才有效)
misfireThreshold: 100
# 集群配置
isClustered: true
# 线程池配置
threadPool:
class: org.quartz.simpl.SimpleThreadPool
# 线程数
threadCount: 10
# 优先级
threadPriority: 5
这里面有quartz的数据源,线程池,集群和misfire相关配置,简单配置,更多的配置可以到官网查看。
配置application.properties 自动生成表
spring.quartz.jdbc.initialize-schema: always
spring.quartz.job-store-type: jdbc
3)实体类
@Data
public class JobInfo {
/**
* 任务名称
*/
private String jobName;
/**
* 任务组
*/
private String jobGroup;
/**
* 触发器名称
*/
private String triggerName;
/**
* 触发器组
*/
private String triggerGroup;
/**
* cron表达式
*/
private String cron;
/**
* 类名
*/
private String className;
/**
* 状态
*/
private String status;
/**
* 下一次执行时间
*/
private String nextTime;
/**
* 上一次执行时间
*/
private String prevTime;
/**
* 配置信息(data)
*/
private String config;
}
4)任务类
/**
* @Auther: 胖叔讲java
* @Date: 2024/1/3 - 01 - 03 - 21:05
* @Decsription: top.psjj.task
* @version: 1.0
* @DisallowConcurrentExecution:这个注解的作用就是同一个任务必须在上一次执行完毕之后,再按照corn时间执行,不会并行执行
* @PersistJobDataAfterExecution:这个注解的作用就是下一个任务用到上一个任务的修改数据(定时任务里面的jobData数据流转)
*/
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
@Slf4j
@Component
public class MyTask extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) {
System.out.println("TimeEventJob正在执行..." + LocalDateTime.now());
// 执行9秒
try {
Thread.sleep(9000);
System.out.println("TimeEventJob执行完毕..." + LocalDateTime.now());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
这个类就是继承的QuartzJobBean,当然也可以实现Job接口,这个类就是任务需要具体执行的业务操作类,类上面添加了两个注解,这两个注解的目的就是让同一个任务必须在上一个任务执行完毕之后再按照触发后续执行,以及定时任务里面的JobDataMap,能够在任务中流转以及修改更新;不添加注解的情况下,JobDataMap里面的数据不能在任务之间流转,以及任务的触发不会参照上一任务是否执行完毕。
5)JobHandle(任务的开关停删操作) ?
@Configuration
public class JobHandler {
@Resource
private Scheduler scheduler;
/**
* 添加任务
*/
@SuppressWarnings("unchecked")
public void addJob(JobInfo jobInfo) throws SchedulerException, ClassNotFoundException {
Objects.requireNonNull(jobInfo, "任务信息不能为空");
// 生成job key
JobKey jobKey = JobKey.jobKey(jobInfo.getJobName(), jobInfo.getJobGroup());
// 当前任务不存在才进行添加
if (!scheduler.checkExists(jobKey)) {
Class<Job> jobClass = (Class<Job>)Class.forName(jobInfo.getClassName());
// 任务明细
JobDetail jobDetail = JobBuilder
.newJob(jobClass)
.withIdentity(jobKey)
.withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup())
.withDescription(jobInfo.getJobName())
.build();
// 配置信息
jobDetail.getJobDataMap().put("config", jobInfo.getConfig());
// 定义触发器
TriggerKey triggerKey = TriggerKey.triggerKey(jobInfo.getTriggerName(), jobInfo.getTriggerGroup());
// 设置任务的错过机制
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule(jobInfo.getCron()).withMisfireHandlingInstructionDoNothing())
.build();
scheduler.scheduleJob(jobDetail, trigger);
} else {
throw new SchedulerException(jobInfo.getJobName() + "任务已存在,无需重复添加");
}
}
/**
* 任务暂停
*/
public void pauseJob(String jobGroup, String jobName) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
if (scheduler.checkExists(jobKey)) {
scheduler.pauseJob(jobKey);
}
}
/**
* 继续任务
*/
public void continueJob(String jobGroup, String jobName) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
if (scheduler.checkExists(jobKey)) {
scheduler.resumeJob(jobKey);
}
}
/**
* 删除任务
*/
public boolean deleteJob(String jobGroup, String jobName) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
if (scheduler.checkExists(jobKey)) {
// 这里还需要先删除trigger相关
//TriggerKey triggerKey = TriggerKey.triggerKey(jobInfo.getTriggerName(), jobInfo.getTriggerGroup());
//scheduler.getTrigger()
//scheduler.rescheduleJob()
return scheduler.deleteJob(jobKey);
}
return false;
}
/**
* 获取任务信息
*/
public JobInfo getJobInfo(String jobGroup, String jobName) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
if (!scheduler.checkExists(jobKey)) {
return null;
}
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
if (Objects.isNull(triggers)) {
throw new SchedulerException("未获取到触发器信息");
}
TriggerKey triggerKey = triggers.get(0).getKey();
Trigger.TriggerState triggerState = scheduler.getTriggerState(triggerKey);
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
JobInfo jobInfo = new JobInfo();
jobInfo.setJobName(jobGroup);
jobInfo.setJobGroup(jobName);
jobInfo.setTriggerName(triggerKey.getName());
jobInfo.setTriggerGroup(triggerKey.getGroup());
jobInfo.setClassName(jobDetail.getJobClass().getName());
jobInfo.setStatus(triggerState.toString());
if (Objects.nonNull(jobDetail.getJobDataMap())) {
jobInfo.setConfig(JSONObject.toJSONString(jobDetail.getJobDataMap()));
}
CronTrigger theTrigger = (CronTrigger) triggers.get(0);
jobInfo.setCron(theTrigger.getCronExpression());
return jobInfo;
}
}
6)Controller(调用接口实现任务操作)
@RestController
@RequestMapping("/job")
public class QuartzController {
@Resource
private JobHandler jobHandler;
@Resource
private Scheduler scheduler;
/**
* 查询所有的任务
*/
@RequestMapping("/all")
public List<JobInfo> list() throws SchedulerException {
List<JobInfo> jobInfos = new ArrayList<>();
List<String> triggerGroupNames = scheduler.getTriggerGroupNames();
for (String triggerGroupName : triggerGroupNames) {
Set<TriggerKey> triggerKeySet = scheduler
.getTriggerKeys(GroupMatcher.triggerGroupEquals(triggerGroupName));
for (TriggerKey triggerKey : triggerKeySet) {
Trigger trigger = scheduler.getTrigger(triggerKey);
JobKey jobKey = trigger.getJobKey();
JobInfo jobInfo = jobHandler.getJobInfo(jobKey.getGroup(), jobKey.getName());
jobInfos.add(jobInfo);
}
}
return jobInfos;
}
/**
* 添加任务
*/
@PostMapping("/add")
public JobInfo addJob(@RequestBody JobInfo jobInfo) throws SchedulerException, ClassNotFoundException {
jobHandler.addJob(jobInfo);
return jobInfo;
}
/**
* 暂停任务
*/
@RequestMapping("/pause")
public void pauseJob(@RequestParam("jobGroup") String jobGroup, @RequestParam("jobName") String jobName)
throws SchedulerException {
jobHandler.pauseJob(jobGroup, jobName);
}
/**
* 继续任务
*/
@RequestMapping("/continue")
public void continueJob(@RequestParam("jobGroup") String jobGroup, @RequestParam("jobName") String jobName)
throws SchedulerException {
jobHandler.continueJob(jobGroup, jobName);
}
/**
* 删除任务
*/
@RequestMapping("/delete")
public boolean deleteJob(@RequestParam("jobGroup") String jobGroup, @RequestParam("jobName") String jobName)
throws SchedulerException {
return jobHandler.deleteJob(jobGroup, jobName);
}
}
单线程运行任务不同任务之间串行,任务A运行时间会响应任务B运行间隔,这是我们不想看到的。而多线程执行任务调度不同任务之间的运行间隔不会相互影响。
- 如果任务调度没有持久化,而任务又是基于动态设置,不是开机自启的,会有一个问题,服务重启之后设置的任务都会失效了。
- 如果任务整合持久化之后,设置的动态任务信息就会保存到数据库,开机自启就会加载这些数据库信息,就会按照原来的设置运行任务。
Quartz是一个开源的作业调度框架,用于在Java应用程序中调度任务。Quartz集群和非集群的区别主要体现在以下几个方面:
高可用性:Quartz集群可以提供高可用性,即使其中一个节点出现故障,其他节点仍然可以继续工作。而非集群模式下,如果应用程序所在的服务器出现故障,任务调度将会停止。
负载均衡:Quartz集群可以通过将任务分配给不同的节点来实现负载均衡。这意味着任务将在集群的各个节点上分布,从而提高系统整体的性能和吞吐量。非集群模式下,所有的任务将在单个节点上运行,可能会导致性能瓶颈。
数据共享:Quartz集群可以共享任务调度的数据,包括作业和触发器等。这意味着当一个节点添加或删除任务时,其他节点也能够感知到。非集群模式下,每个节点都有自己独立的任务调度数据,可能导致数据不一致。
【1】简述一下什么是任务调度?
任务调度就是按照特定时间规则执行系统某个固定的业务逻辑。任务调度底层是使用jdk的Timer实现的。单体项目建议使用Spring-task任务调度技术,分布式架构建议使用quartz任务调度框架。Spring-task是单线程运行旳,Quartz是多线程运行的,且功能更为丰富,支持作业管理。
【2】说一下你都用过什么任务调度技术,他们的区别是什么?
Spring-task是单线程,且功能简单。执行任务只需开启开关@EnableScheduling,在要执行的任务方法上加@Scheduled(cron = "*/1 * * * * *")注解。
它的使用弊端:
任务A的执行时间会影响任务B的执行间隔,但是任务A和任务B是两个任务,不应该相互影响。
没有固定组件,持久化等功能,也就没法形成作业系统
Quartz是多线程的高可用的任务调度框架,支持持久化,多线程,集群模式,且有固定组件结构Job、Trigger、scheduler。他的优点:
有固定组件,有持久化功能,这样就能基于Quartz开发一个任务调度系统,通过UI界面去管理任务调度。
任务进行持久化之后,重启服务器会加载持久化的任务继续执行。
任务支持集群模式,如果任务调度模块是一个集群n个节点,那么任务调度不会因为一个节点挂掉而挂掉,且任务在集群之间形成负载均衡。