阶段十-分布式-任务调度
第一章 定时任务概述
在项目中开发定时任务应该一种比较常见的需求,在 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集群和非集群的区别主要体现在以下几个方面:
-
高可用性: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个节点,那么任务调度不会因为一个节点挂掉而挂掉,且任务在集群之间形成负载均衡。
相关文章:
阶段十-分布式-任务调度
第一章 定时任务概述 在项目中开发定时任务应该一种比较常见的需求,在 Java 中开发定时任务主要有三种解决方案:一是使用JDK 自带的 Timer,二是使用 Spring Task,三是使用第三方组件 Quartz Timer 是 JDK 自带的定时任务工具,其…...
Godot4.2——爬虫小游戏简单制作
目录 一、项目 二、项目功能 怪物 人物 快捷键 分数 游戏说明 提示信息 三、学习视频 UI制作 游戏教程 四、总结 一、项目 视频演示:Godot4爬虫小游戏简单制作_哔哩哔哩bilibili 游戏教程:【小猫godot4入门教程 C#版 已完结】官方入门案例 第…...
对象的前世今生与和事佬(static)的故事
目录 1.对象村的秘密(对象在内存的实现) 1.1 内存的好兄弟“堆”与“栈” 1.1.1方法喜欢玩泰山压顶 1.1.2 stack的实现 1.2栈上的对象引用 1.2.1有关对象局部变量 1.2.2 如果局部变量生存在栈上,那么实例变量呢? 1.2.3创建…...
报错curl: (6) Could not resolve host: raw.githubusercontent...的解决办法
我起初想要在macOS系统安装pip包,首先在终端安装homebrew,敲了命令:/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent...)" 之后触发的报错,报错内容:curl: (6) Could not resolve host: raw.…...
【基础篇】十二、引用计数法 可达性分析算法
文章目录 1、Garbage Collection2、方法区的回收3、堆对象回收4、引用计数法5、可达性分析算法6、查看GC Root对象 1、Garbage Collection C/C,无自动回收机制,对象不用时需要手动释放,否则积累导致内存泄漏: Java、C#、Python、…...
C语言算法(二分查找、文件读写)
二分查找 前提条件:数据有序,随机访问 #include <stdio.h>int binary_search(int arr[],int n,int key);int main(void) {}int search(int arr[],int left,int right,int key) {//边界条件if(left > right) return -1;//int mid (left righ…...
流媒体学习之路(WebRTC)——Pacer与GCC(5)
流媒体学习之路(WebRTC)——Pacer与GCC(5) —— 我正在的github给大家开发一个用于做实验的项目 —— github.com/qw225967/Bifrost目标:可以让大家熟悉各类Qos能力、带宽估计能力,提供每个环节关键参数调节接口并实现一个json全…...
2023版本QT学习记录 -11- 多线程的使用(QT的方式)
———————多线程的使用(QT方式)——————— 🎄效果演示 两个线程都输出一些调试信息 🎄创建多线程的流程 🎄头文件 #include "qthread.h"🎄利用多态重写任务函数 class rlthread1 : public QThread {Q_OBJE…...
iOS苹果和Android安卓测试APP应用程序的差异
Hello大家好呀,我是咕噜铁蛋!我们经常需要关注移动应用程序的测试和优化,以提供更好的用户体验。在移动应用开发领域,iOS和Android是两个主要的操作系统平台。本文铁蛋讲给各位小伙伴们详细介绍在App测试中iOS和Android的差异&…...
每日算法打卡:数的三次方根 day 7
文章目录 原题链接题目描述输入格式输出格式数据范围输入样例:输出样例: 题目分析示例代码 原题链接 790. 数的三次方根 题目难度:简单 题目描述 给定一个浮点数 n,求它的三次方根。 输入格式 共一行,包含一个浮…...
人机交互主板定制_基于MT8735安卓核心板的自助查询机方案
人机交互主板是一种商显智能终端主板,广泛应用于广告机、工控一体机、教学一体机、智能自助终端、考勤机、智能零售终端、O2O智能设备、取号机、计算机视觉、医疗健康设备、机器人设备等领域。 人机交互主板采用联发科MTK8735芯片平台,四核Cortex-A53架构…...
全志F1C100s Linux 系统编译出错:不能连接 github
环境 Ubuntu 20.04 LTS 64 位虚拟机 开发板:Lichee Pi Nano 源代码:GitHub - florpor/licheepi-nano 问题描述 该源码库使用了 git 子模块的概念,一个库中包含了 u-boot、Linux等代码库。不需要分别编译,一个 make 全搞定 编译时提示错误: >>> linux-hea…...
如何保障 MySQL 和 Redis 的数据一致性?
数据一致性问题是如何产生的? 数据一致性问题通常产生于数据在不同的时间点、地点或系统中存在多个副本的情况, 系统只存在一个副本的情况下也完全可能会产生。 设想一下,你在一家连锁咖啡店有一张会员卡这张会员卡可以绑定两个账号&#x…...
Leetcode 2999. Count the Number of Powerful Integers
Leetcode 2999. Count the Number of Powerful Integers 1. 解题思路2. 代码实现 题目链接:10034. Count the Number of Powerful Integers 1. 解题思路 这一题的话其实还是一个典型的求不大于 N N N的特殊数字个数的问题。 这道题本质上进行一下替换还是要求如…...
【Reading Notes】(2)
文章目录 FreestyleHip-hop dance and MusicProgrammerMaster Freestyle 都说人的成长有三个阶段,第一个阶段认为自己独一无二,天之骄子;第二个阶段发现自己原来如此渺小,如此普通,沮丧失望;第三阶段&#…...
【设计模式之美】SOLID 原则之一:怎么才算是单一原则、如何取舍单一原则
文章目录 一. 如何判断类的职责是否足够单一?二. 类的职责是否设计得越单一越好? 开始学习一些经典的设计原则,其中包括,SOLID、KISS、YAGNI、DRY、LOD 等。 本文主要学习单一职责原则的相关内容。 单一职责原则的定义:…...
# [NOIP2015 普及组] 扫雷游戏#洛谷
题目背景 NOIP2015 普及组 T2 题目描述 扫雷游戏是一款十分经典的单机小游戏。在 n n n 行 m m m 列的雷区中有一些格子含有地雷(称之为地雷格),其他格子不含地雷(称之为非地雷格)。玩家翻开一个非地雷格时&#…...
Unity中Shader的_Time精度问题
文章目录 前言一、U方向上优化二、V方向上优化在这里插入图片描述 三、最终代码1、效果2、Shader 前言 在Unity的Shader中,使用了_Time来达到UV的流动效果,普遍会出现一个问题。我们的UV值会随着时间一直增加(uv值增加了,但是因为…...
听GPT 讲Rust源代码--compiler(15)
File: rust/compiler/rustc_arena/src/lib.rs 在Rust源代码中,rustc_arena/src/lib.rs文件定义了TypedArena,ArenaChunk,DroplessArena和Arena结构体,以及一些与内存分配和容器操作相关的函数。 cold_path<F: FnOnce,drop,new,…...
关键字联合体union的定义和使用
联合体的定义 联合体的定义和结构体相同。 联合体成员共用存储空间,联合体占用的空间最大长度的数据成员的长度。 union State {char sleep;char run;int suspend;double error; }state_u;以上例子,State表示联合体的名字,它相当于声明了一…...
基于GA-PSO遗传粒子群混合优化算法的VRPTW问题求解matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 遗传算法(GA)基本原理 4.2 粒子群优化(PSO)基本原理 4.3 算法优化策略 5.完整程序 1.程序功能描述 VRPTW是车辆路径问题(VR…...
【leetcode100-033】【链表】排序链表
【题干】 给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。 【思路】 递归版归并法链表版~没什么特别好说的(非递归版归并也是可以哒,但是马上要考试了今天懒得写了!打个flag在这里也许哪天想起来…...
[Kubernetes]5. k8s集群StatefulSet详解,以及数据持久化(SC PV PVC)
前面通过deployment结合service来部署无状态的应用,下面来讲解通过satefulSet结合service来部署有状态的应用 一.StatefulSet详解 1.有状态和无状态区别 无状态: 无状态(stateless)、牲畜(cattle)、无名(nameless)、可丢弃(disposable) 有状态: 有状态(stateful)、宠物(pet)…...
数据库系统-甘晴void学习笔记
数据库系统笔记 计科210X 甘晴void 202108010XXX 教材:《数据库系统概论》第6版 (图片来源于网络,侵删) 文章目录 数据库系统<br>笔记第一篇 基础篇1 绪论1.1数据库系统概述1.2数据模型1.3数据库系统的结构(三级模式结构…...
Azure Machine Learning - 人脸识别任务概述与技术实战
Azure AI 人脸服务提供了可检测、识别和分析图像中的人脸的 AI 算法。 人脸识别软件在许多不同情形中都十分重要,例如识别、无接触访问控制和实现隐私的人脸模糊。你可以通过客户端库 SDK,或者直接调用 REST API 使用人脸服务。 目录 一、人脸识别服务场…...
强化学习的数学原理学习笔记 - 蒙特卡洛方法(Monte Carlo)
文章目录 概览:RL方法分类蒙特卡洛方法(Monte Carlo,MC)MC BasicMC Exploring Starts🟦MC ε-Greedy 本系列文章介绍强化学习基础知识与经典算法原理,大部分内容来自西湖大学赵世钰老师的强化学习的数学原理…...
DDIA 第十一章:流处理
本文是《数据密集型应用系统设计》(DDIA)的读书笔记,一共十二章,我已经全部阅读并且整理完毕。 采用一问一答的形式,并且用列表形式整理了原文。 笔记的内容大概是原文的 1/5 ~ 1/3,所以你如果没有很多时间…...
webpack知识点总结(高级应用篇)
除开公共基础配置之外,我们意识到两点: 1. 开发环境(modedevelopment),追求强大的开发功能和效率,配置各种方便开 发的功能;2. 生产环境(modeproduction),追求更小更轻量的bundle(即打包产物); 而所谓高级应用,实际上就是进行 Webpack 优化…...
均匀与准均匀 B样条算法
B 样条曲线的定义 p ( t ) ∑ i 0 n P i F i , k ( t ) p(t) \sum_{i0}{n} P_i F_{i, k}(t) p(t)i0∑nPiFi,k(t) 方程中 n 1 n1 n1 个控制点, P i P_i Pi, i 0 , 1 , ⋯ n i0, 1, \cdots n i0,1,⋯n 要用到 n 1 n1 n1 个 k k k 次 B 样条基函数 …...
2023年12 月电子学会Python等级考试试卷(一级)答案解析
青少年软件编程(Python)等级考试试卷(一级) 分数:100 题数:37 一、单选题(共25题,共50分) 1. 下列程序运行的结果是?( ) print(hello) print(world) A. helloworld...
网站怎么做微博链接/html网站模板免费
2019独角兽企业重金招聘Python工程师标准>>> ###vagrant 简明使用方法 安装box 需要安装virtual box 和 vagrant 去官网找自己电脑所对应的版本即可 然后在终端执行: vagrant box add 发布者/系统名称这样会通过vagrantcloud上来根据 发布者/系统名称来下载box 通常…...
做检测设备的网站有哪些/自己开发网站
常用路由协议的梳理静态路由:优点:无需进行路由交换、更高的安全性、有的情况下必须使用静态路由,如DDR、使用NAT技术的网络环境。缺点:网络的扩展性能差、配置烦琐。动态路由:(Rip、OSPF、EIGRP、BGP)Rip具…...
如何在网站上做网盘/企业网站优化软件
2022年已经开始了,在新的一年里,又要大干一场了。工欲善其事必先利其器,计划做完之后,总要有能记录待办事项以及任务清单的应用,可以记录自己要做的事情,方便自己及时查看自己有哪些事情没有做,…...
网站开源程序/seo营销的概念
Annotation(注释) 概述从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是 Annotation(注释) Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理. 通过使用 Annotation, 程序员可以在不改变原有逻辑的情况下, 在…...
深圳小程序开发定制公司/搜索引擎营销就是seo
阅读目录(Content)MTV模型Django基本命令1、下载Django:2、创建一个django project3、在mysite目录下创建应用4、启动django项目5、同步更改数据库表或字段6、清空数据库文件配置1.静态文件配置static配置:media配置:2.其他配置模板路径配置(…...
深圳工业设计展2024/深圳网站营销seo电话
正则表达式匹配,其中: * ~ 为区分大小写匹配 * ~* 为不区分大小写匹配 * !~和!~*分别为区分大小写不匹配及不区分大小写不匹配 文件及目录匹配,其中: * -f和!-f用来判断是否存在文件 * -d和!-d用来判断是否存在目录 * -e和!-e…...