阶段十-分布式-任务调度

第一章 定时任务概述

在项目中开发定时任务应该一种比较常见的需求,在 Java 中开发定时任务主要有三种解决方案:一是使用JDK 自带的 Timer,二是使用 Spring Task,三是使用第三方组件 Quartz

Timer 是 JDK 自带的定时任务工具,其简单易用,但是对于复杂的定时规则无法满足,在实际项目开发中也很少使用到。而 Spring Task使用起来很简单,除 Spring 相关的包外不需要额外的包,而且支持注解和配置文件两种形式。 Quartz 功能强大,但是使用起来相对笨重。

建议:

  • 单体项目架构使用Spring Task

  • 分布式项目架构使用Quartz

第二章 JDK实现任务调度

/*** 基于jdk的任务调度*/
public class JdkTaskDemo {public static void main(String[] args) {//创建定时类Timer timer = new Timer();//创建任务类TimerTask task = new TimerTask() {@Overridepublic void run() {System.out.println("定时任务执行了......"+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));}};//执行定时任务timer.schedule(task,new Date(),2000);}
}

第三章 Spring-task实现任务调度

3.1 节 Spring-task入门案例

【1】搭建SpringBoot工程,导入spring-boot-starter-web即可,不需导入任何其他依赖,pom如下

<?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】编写任务类测试

//创建测试任务类//放入容器中
@Component
public class SpringTask {//使用@Scheduled 注解 配合 cron 表达式@Scheduled(cron = "*/1 * * * * *")public void task1() throws  InterruptedException {System.out.println(Thread.currentThread().getName()+"task1-->"+LocalDateTime.now());}
}

3.2 节 Spring-task 分析

接下来,我们编写任务调度,可调度程序加一下执行时间,模拟真实业务。

//创建测试任务类//放入容器中
@Component
public class SpringTask {//使用@Scheduled 注解 配合 cron 表达式@Scheduled(cron = "*/1 * * * * *")public void task1() throws  InterruptedException {System.out.println(Thread.currentThread().getName()+"task1-->"+LocalDateTime.now());Thread.sleep(5000);}
}

任务并不是每秒执行一次,而是六秒执行一次

得出结论:

1.Spring-task 执行任务按照单线程执行并合理执行,不会因为第一个执行任务时间过长而去执行第二个任务

2.Spring-task是单线程的处理任务,处理任务能力有限,不建议处理分布式架构的任务调度

比如说:

@Component
public class MyTask {@Scheduled(cron = "*/1 * * * * *") //每秒执行1次public void task2() throws InterruptedException {System.out.println(Thread.currentThread().getName()+":task2--->"+ LocalDateTime.now());}@Scheduled(cron = "*/1 * * * * *") //每秒执行1次public void task1() throws InterruptedException {System.out.println(Thread.currentThread().getName()+":task1--->"+ LocalDateTime.now());Thread.sleep(5000);}
}

定时任务2 1s时间执行完但受任务1影响也需要等5s。  

3.3 节 Cron表达式讲解

关于 cronExpression 表达式有至少 6 个(也可能是 7 个)由空格分隔的时间元素。从左至右,这些元素的定义如下:

1.秒(0–59)

2.分钟(0–59)

3.小时(0–23)

4.月份中的日期(1–31)

5.月份(1–12 或 JAN–DEC)

6.星期中的日期(1–7 或 SUN–SAT)

7.年份(1970–2099)

逗号为并列作用

0 0 10,14,16 * * ? 每天上午 10 点,下午 2 点和下午 4 点 

- 是代表1到10天 

0 0,15,30,45 * 1-10 * ? 每月前 10 天每隔 15 分钟 
30 0 0 1 1 ? 2012 在 2012 年 1 月 1 日午夜过 30 秒时 

各个时间可用值如下:

秒 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 , - * /

可用值详细分析如下:

"*" —— 字符可以用于所有字段,在"分"字段中设为"*",表示"每一分钟"的含义。
"?" —— 字符可以用在"日"和"周几"字段,它用来指定"不明确的值"。这在你需要指定这两个字段中的某一个值而不是另外一个的时候会被用到。在后面的例子中可以看到其含义。
"-" —— 字符被用来指定一个值的范。比如在"小时"字段中设为"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",则表示"日历中的第一天或者周日以后"。对于"月份"字段和"周几"字段来说合法的字符都不是大小写敏感的。

备注:以上读一遍有印象即可,实际开发在线文档自动生成

在线Cron表达式生成器

第四章 Quartz 基本应用

4.1 节 Quartz 介绍

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:

  • 持久性作业 - 就是保持调度定时的状态;

  • 作业管理 - 对调度作业进行有效的管理;

官方文档:

  • Documentation

  • Quartz Enterprise Job Scheduler 2.3.0-SNAPSHOT API

4.2 节 Quartz API 介绍

 Quartz 的核心类有以下三部分:

  • 任务Job: 需要实现的任务类,实现execute()方法,执行后完成任务
  • 触发器Trigger :包括SimpleTrigger 和 CronTrigger
  • 调度器Scheduler:任务调度器,负责基于Trigger触发器,来执行job任务

4.3 节 Quartz 入门案例

1)创建springboot工程,导入依赖

pom如下:

<?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)新建任务类

public class MyJob implements Job {@Overridepublic 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();}
}

4)测试结果

每3s执行一次

第五章 QuartzAPI详细讲解

5.1 节 JobDetail

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实例,这样就可以 规避并发访问 的问题。

携带参数案例

package com.sh;import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.scheduling.annotation.Scheduled;/*** @Auther: 世豪讲java* @Date: 2024/1/4 - 01 - 04 - 20:27* @Decsription: com.sh* @version: 1.0*/
public class QuartzTest {//test方法之后执行一次,所以使用main方法public static void main(String[] args) throws SchedulerException {//1.创建任务调度器SchedulerFactory factory = new StdSchedulerFactory();Scheduler scheduler = factory.getScheduler();//创建JobDataMap,可携带的参数JobDataMap jobDataMap = new JobDataMap();jobDataMap.put("param1","value1");jobDataMap.put("param2","value2");//2.创建JobDetail实例,与MyJob类绑定JobDetail job = JobBuilder.newJob(MyJob.class)//指定任务名,组名.withIdentity("job1","group1")//携带参数JobDataMap.setJobData(jobDataMap).build();//3.构建Trigger实例,每个3s执行一次Trigger trigger = TriggerBuilder.newTrigger()//指定触发器名字,组名.withIdentity("trigger1","group1")//什么时候触发.startNow()//触发规则,3秒触发一次//简单的SimpleScheduleBuilder构建起.withSchedule(SimpleScheduleBuilder.simpleSchedule()//每3s触发一次.withIntervalInSeconds(3)//永远触发.repeatForever()).build();//4.调度器执行任务//把job和trigger给schedulescheduler.scheduleJob(job,trigger);System.out.println(System.currentTimeMillis());//启动任务scheduler.start();}
}
public class MyJob implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {//获取jobDataMapJobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();System.out.println("任务被执行了"+jobDataMap.get("param1")+jobDataMap.get("param2"));}
}

5.2 节 SimpleTrigger

这是比较简单的一类触发器,用它能实现很多基础的应用。使用它的主要场景包括:

  • 在指定时间段内,执行一次任务

最基础的 Trigger 不设置循环,设置开始时间。

  • 在指定时间段内,循环执行任务

在 1 基础上加上循环间隔。可以指定 永远循环、运行指定次数

示例:

Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger2","group1").startNow().withSchedule(//使用简单触发器SimpleScheduleBuilder.simpleSchedule().//3s间隔执行withIntervalInSeconds(3).//执行6次 count+1withRepeatCount(5)).build();

5.3 节 CronTrigger

CronTrigger 是基于日历的任务调度器,在实际应用中更加常用。虽然很常用,但是知识点都一样,只是可以通过表达式来设置时间而已。使用方式就是绑定调度器时换一下:

Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger2","group1").startNow().withSchedule(//使用日历触发器CronScheduleBuilder.cronSchedule("0/1 * * * * ? ")).build();

第六章 SpringBoot整合Quartz

6.1 节 SpringBoot整合Quartz

接下来实现SpringBoot整合Quartz动态实现任务调度;动态任务调度ui参考

该UI需要自己创建

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.Driverusername: rootpassword: 123456url: 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/Shanghaidriver: com.mysql.cj.jdbc.DrivermaxConnections: 5username: rootpassword: 123456# 必须指定数据源类型provider: hikaricpscheduler:instanceName: globalScheduler# 实例idinstanceId: AUTOtype: com.alibaba.druid.pool.DruidDataSourcejobStore:# 数据源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相关配置,简单配置,更多的配置可以到官网查看。

Configuration Reference

配置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)任务类

 * @DisallowConcurrentExecution:这个注解的作用就是同一个任务必须在上一次执行完毕之后,再按照corn时间执行,不会并行执行* @PersistJobDataAfterExecution:这个注解的作用就是下一个任务用到上一个任务的修改数据(定时任务里面的jobData数据流转)*/@DisallowConcurrentExecution
@PersistJobDataAfterExecution
@Slf4j
@Component
public class MyTask extends QuartzJobBean {@Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {System.out.println("任务1正在执行..." + LocalDateTime.now());// 执行9秒try {Thread.sleep(9000);System.out.println("任务1执行完毕..." + LocalDateTime.now());} catch (InterruptedException e) {throw new RuntimeException(e);}}
}

这个类就是继承的QuartzJobBean,当然也可以实现Job接口,这个类就是任务需要具体执行的业务操作类,类上面添加了两个注解,这两个注解的目的就是让同一个任务必须在上一个任务执行完毕之后再按照触发后续执行,以及定时任务里面的JobDataMap,能够在任务中流转以及修改更新;不添加注解的情况下,JobDataMap里面的数据不能在任务之间流转,以及任务的触发不会参照上一任务是否执行完毕。

5)JobHandle(任务的开关停删操作)

@Configuration
public class JobHandler {@Resourceprivate Scheduler scheduler;/*** 添加任务*/@SuppressWarnings("unchecked")public void addJob(JobInfo jobInfo) throws SchedulerException, ClassNotFoundException {Objects.requireNonNull(jobInfo, "任务信息不能为空");// 生成job keyJobKey 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 {@Resourceprivate JobHandler jobHandler;@Resourceprivate 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);}
}

7)测试

6.2 节 如何实现开启服务自动执行任务

@PostConstructpublic  void  init(){addMyTask();}public void addMyTask(){try {JobInfo jobInfo = new JobInfo();jobInfo.setJobGroup("group1");jobInfo.setJobName("job2");jobInfo.setCron("0/1 * * * * ?");jobInfo.setClassName("com.sh.task.MyTask");jobInfo.setTriggerName("trigger1");jobInfo.setTriggerGroup("triggerGroup1");this.addJob(jobInfo);} catch (SchedulerException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}

6.3 节 单线程与多线程执行任务调度的区别

6.3.1 单线程运行任务

结论:单线程运行任务不同任务之间串行,任务A运行时间会响应任务B运行间隔

6.3.2 多线程执行任务

再创建一个mytask2

@DisallowConcurrentExecution
@PersistJobDataAfterExecution
@Slf4j
@Component
public class MyTask2 extends QuartzJobBean {@Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {log.info("任务2执行..." + LocalDateTime.now());}
}

 

@PostConstruct
public void init(){addMyTask();addMyTask2();
}
public void addMyTask() {try {JobInfo jobInfo = new JobInfo();jobInfo.setJobName("job1");jobInfo.setJobGroup("group1");jobInfo.setTriggerName("trigger1");jobInfo.setTriggerGroup("triggerGroup1");jobInfo.setClassName("top.psjj.task.MyTask");jobInfo.setCron("0/1 * * * * ? *");this.addJob(jobInfo);} catch (SchedulerException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}
}
public void addMyTask2() {try {JobInfo jobInfo = new JobInfo();jobInfo.setJobName("job2");jobInfo.setJobGroup("group1");jobInfo.setTriggerName("trigger2");jobInfo.setTriggerGroup("triggerGroup1");jobInfo.setClassName("top.psjj.task.MyTask2");jobInfo.setCron("0/1 * * * * ? *");this.addJob(jobInfo);} catch (SchedulerException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}
}

显然任务2还是1s执行一次,没有收到任务1执行时间干扰,这是任务2和任务1用的线程不同。

任务二不受任务一的影响

6.4 节 任务调度持久化的好处

如果任务调度没有持久化,而任务又是基于动态设置,不是开机自启的,会有一个问题,服务重启之后设置的任务都会失效了。如果任务整合持久化之后,设置的动态任务信息就会保存到数据库,开机自启就会加载这些数据库信息,就会按照原来的设置运行任务。

但是,一定要把建表配置注释掉

#spring.quartz.jdbc.initialize-schema: always
#spring.quartz.job-store-type: jdbc

6.5 节 Quartz 集群执行与单机执行区别

Quartz是一个开源的作业调度框架,用于在Java应用程序中调度任务。Quartz集群和非集群的区别主要体现在以下几个方面:

  1. 高可用性:Quartz集群可以提供高可用性,即使其中一个节点出现故障,其他节点仍然可以继续工作。而非集群模式下,如果应用程序所在的服务器出现故障,任务调度将会停止。

  2. 负载均衡:Quartz集群可以通过将任务分配给不同的节点来实现负载均衡。这意味着任务将在集群的各个节点上分布,从而提高系统整体的性能和吞吐量。非集群模式下,所有的任务将在单个节点上运行,可能会导致性能瓶颈。

  3. 数据共享:Quartz集群可以共享任务调度的数据,包括作业和触发器等。这意味着当一个节点添加或删除任务时,其他节点也能够感知到。非集群模式下,每个节点都有自己独立的任务调度数据,可能导致数据不一致。

需要注意的是,Quartz集群需要配置和管理多个节点,可能需要更多的系统资源和维护工作。非集群模式则相对简单,适用于小规模的应用程序。选择使用哪种模式应根据具体的需求和系统要求来决定。

 

第七章 总结

【1】简述一下什么是任务调度?

答:任务调度就是按照特定时间规则执行系统某个固定的业务逻辑。任务调度底层是使用jdk的Timer实现的。单体项目建议使用Spring-task任务调度技术,分布式架构建议使用quartz任务调度框架。Spring-task是单线程运行旳,Quartz是多线程运行的,且功能更为丰富,支持作业管理。

【2】说一下你都用过什么任务调度技术,他们的区别是什么?

答:Spring-task是单线程,且功能简单。执行任务只需开启开关@EnableScheduling,在要执行的任务方法上加

@Scheduled(cron = "*/1 * * * * *")注解。它的使用弊端:

  1. 任务A的执行时间会影响任务B的执行间隔,但是任务A和任务B是两个任务,不应该相互影响。

  2. 没有固定组件,持久化等功能,也就没法形成作业系统

Quartz是多线程的高可用的任务调度框架,支持持久化,多线程,集群模式,且有固定组件结构Job、Trigger、scheduler。他的优点一一说明

  1. 有固定组件,有持久化功能,这样就能基于Quartz开发一个任务调度系统,通过UI界面去管理任务调度。

  2. 任务进行持久化之后,重启服务器会加载持久化的任务继续执行。

  3. 任务支持集群模式,如果任务调度模块是一个集群n个节点,那么任务调度不会因为一个节点挂掉而挂掉,且任务在集群之间形成负载均衡。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/597108.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

感知与认知的碰撞,大模型时代的智能文档处理范式

目录 0 写在前面1 GPT4-V&#xff1a;拓宽文档认知边界2 大语言模型的文档感知缺陷3 大一统文档图像处理范式3.1 像素级OCR任务3.2 OCR大一统模型3.3 长文档理解与应用 4 总结抽奖福利 0 写在前面 由中国图象图形学学会青年工作委员会发起的第十九届中国图象图形学学会青年科学…

ElasticSearch使用Grafana监控服务状态-Docker版

文章目录 版本信息构建docker-compose.yml参数说明 创建Prometheus配置文件启动验证配置Grafana导入监控模板模板说明 参考资料 版本信息 ElasticSearch&#xff1a;7.14.2 elasticsearch_exporter&#xff1a;1.7.0&#xff08;latest&#xff09; 下载地址&#xff1a;http…

如何使用 Python+selenium 进行 web 自动化测试?

Selenium是一个自动化测试工具&#xff0c;它可以模拟用户在浏览器中的操作&#xff0c;比如点击、输入、选择等等。它支持多种浏览器&#xff0c;包括Chrome、Firefox、Safari等等&#xff0c;并且可以在多个平台上运行。 安装和配置Selenium 在使用Selenium之前&#xff0c;…

【VTK三维重建-体绘制】第五期 vtkLODProp3D

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ&#xff1a;870202403 前言 本文分享VTK中体绘制中的vtkLODProp3D对象&#xff0c;希望对各位小伙伴有所帮助&#xff01; 感谢各位小伙伴的点赞关注&#xff0c;小易会继续努力分享&#xff0c;一起进步&#xff01; 你的点赞…

(Java企业 / 公司项目)Nacos的怎么搭建多环境配置?(含相关面试题)(二)

上一篇讲了一个单体服务中配置&#xff0c;传统的Nacos配置但是在微服务架构当中肯定都是多环境下配置&#xff0c;比如生产环境&#xff0c;dev测试环境等等。 第一种方式模拟开始&#xff1a; 首先展示在生产环境中nacos如何配置&#xff0c;在模块下新建一个配置文件&…

达梦数据库安装超详细教程(小白篇)

文章目录 达梦数据库一、达梦数据库简介二、达梦数据库下载三、达梦数据库安装1. 解压2. 安装 四、初始化数据库五、DM管理工具 达梦数据库 一、达梦数据库简介 ​ 达梦数据库管理系统是达梦公司推出的具有完全自主知识产权的高性能数据库管理系统&#xff0c;简称DM。 达梦数…

本地站点通过主备冗余专线连接上云

本地站点通过主备冗余专线连接上云 您可以使用两条物理专线以主备方式将本地数据中心IDC&#xff08;Internet Data Center&#xff09;接入阿里云。采用主备接入方式时&#xff0c;正常情况下仅主用线路在进行流量转发。阿里云按照您配置的健康检查的发包时间间隔探测主用线路…

【Midjourney】AI绘画新手教程(一)登录和创建服务器,生成第一幅画作

一、登录Discord 1、访问Discord官网 使用柯學尚网&#xff08;亲测非必须&#xff0c;可加快响应速度&#xff09;访问Discord官方网址&#xff1a;https://discord.com 选择“在您的浏览器中打开Discord” 然后&#xff0c;注册帐号、购买套餐等&#xff0c;在此不做缀述。…

uniapp+echarts开发APP版本教程

需求 需要在uniappecharts展示图表功能&#xff0c;是APP版本&#xff0c;不是小程序。找了好多教程都乱七八糟的&#xff0c;无法实现。以下是效果图 教程 1、安装插件 HBuilder安装echarts插件&#xff0c;插件地址如下。安装完成后在uni_modules/lime-echart文件夹下 ht…

微信小程序+前后端开发学习材料

目录结构 全局文件 1.app.json 文件 用来对微信小程序进行全局配置&#xff0c;决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。文件内容为一个 JSON 对象。 1.1 page用于指定小程序由哪些页面组成&#xff0c;每一项都对应一个页面的 路径&#xff08;含文…

docker镜像仓库详解(Docker Registry)

本片文章主要是对docker的镜像仓库进行了详解。其中包含了一些常用了 docker 指令&#xff0c;通过举例进行详解。也详细解释了镜像仓库的工作机制和常见的镜像仓库。也实际拉去和运行了一些镜像。希望本篇文章会对你有所帮助&#xff01; 文章目录 一、什么是Docker Registry …

网工内推 | 事业单位、上市公司网工,五险一金补贴多

01 中科中山药物创新研究院 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1.负责信息化网络数据安全&#xff0c;加固网络安全、渗透测试、风险评估、漏洞扫描、风险管理和风险评估方法。对防火墙、IDS、IPS、蜜罐、防DDOS、VPN等设备的运行管理&#xff1b; 2.负责机…

CodeWave智能开发平台--03--目标:应用创建--01模板创建依赖问题修改

摘要 本文是网易数帆CodeWave智能开发平台系列的第03篇&#xff0c;主要介绍了基于CodeWave平台文档的新手入门进行学习&#xff0c;实现一个完整的应用&#xff0c;本文主要完成模板创建时的依赖问题解决。 CodeWave智能开发平台的03次接触 CodeWave参考资源 网易数帆Code…

脚本接收参数,执行shell指令

洗礼 上午开完会&#xff0c;5个小时内丧失思考行动能力&#xff0c;10个小时内不敢说话&#xff0c;深刻把握会议精神&#xff0c;反省自己。因为经常用到脚本执行任务&#xff0c;所以记录一下一些常用的脚本接收参数&#xff0c;执行shell指令。 python脚本 Python脚本是…

vercel部署Gemini pro

一、注册一个vercel账号&#xff08;这个东西类似于第三方的github pages&#xff0c;能部署github中的项目&#xff09; 二、注册结束后&#xff0c;填写github的账号&#xff08;需要事先在该github账号中fork一个gemini的repository&#xff09; 三、babaohuang/GeminiPro…

阶段十-分布式-Redis01

第一章 Redis入门 1.1 节 什么是NoSql型数据库 NoSQL ,泛指非关系型的数据库, NoSQL Not Only SQL,它可以作为关系型数据库的良好补充。 NoSQL 不依赖业务逻辑方式存储&#xff0c;而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。NoSQL与SQL数据库区别对比如…

CMake入门教程【核心篇】包含目录(include_directories)

&#x1f608;「CSDN主页」&#xff1a;传送门 &#x1f608;「Bilibil首页」&#xff1a;传送门 &#x1f608;「本专栏容」&#xff1a;CMake入门教程 &#x1f608;「动动你的小手」&#xff1a;点赞&#x1f44d;收藏⭐️评论&#x1f4dd; 文章目录 1. 基本用法2. 添加单个…

快手推荐算法工程师三面回顾

快手三次技术面试一次HR面试的简单回顾&#xff0c;希望对大家有所启发。 一面 面试官一上来就让写算法题&#xff0c;第一个是计算岛屿数量&#xff0c;第二个是最长回文字串。 然后就是介绍自己的论文。对于论文的工作&#xff0c;面试官只是在问关于论文的问题&#xff0…

【PCB专题】Allegro封装更新焊盘

在PCB封装的绘制中&#xff0c;有时会出现需要更新焊盘的情况。比如在制作封装的过程中发现焊盘做的不对而使用PAD_Designer重新更新了焊盘。 那在PCB中如何更新已经修改过的焊盘呢&#xff1f; 打开封装&#xff0c;选择Tools->Padstack->Refresh... 选择Refresh all …

中国FinOps现状调查报告(2023)》亮点解读

今年以来&#xff0c;我们举办了多期FinOps的专题分享&#xff0c;邀请了美图、腾讯、B站、趣丸、知乎等厂商和行业专家&#xff0c;分享他们在FinOps领域的经验。我们也发现越来越多的人对FinOps产生了浓厚的兴趣&#xff0c;而且FinOps的成熟度也在逐渐提升。 降本增效&…