七:Day08_任务调度

news2025/2/27 10:28:56

第一章 定时任务概述

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

建议:

  • 单体项目架构使用Spring Task(支持注解和配置文件两种形 )。

  • 分布式项目架构使用Quartz

第二章 JDK实现任务调度


/**
 * 基于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);
    }
}

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

3.1 节 Spring-task入门案例

【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】运行结果

3.2 节 Spring-task 分析

Spring-task执行的任务是基于单线程执行的。由此得出两个结论:

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

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

3.3 节 Cron表达式讲解

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

  1. 秒        0-59 , - * /
  2. 分        0-59 , - * /
  3. 小时     0-23 , - * /
  4. 日         1-31 , - * ? / L W C
  5. 月         1-12 or JAN-DEC , - * /
  6. 周几      1-7 or SUN-SAT , - * ? / L C #
  7. 年(可选字段) 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

实际开发在线文档自动生成

在线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:包括SimpleTriggerCronTrigger
  • 调度器Scheduker:任务调度器,负责基于 Trigger触发器,来执行 Job任务。

4.3 节 Quartz 入门案例

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();
    }
}

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

5.2 节 SimpleTrigger

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

  1. 在指定时间段内,执行一次任务。最基础的 Trigger 不设置循环,设置开始时间。

  2. 在指定时间段内,循环执行任务。在 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();

5.3 节 CronTrigger

CronTrigger 是基于日历的任务调度器,在实际应用中更加常用。,但是知识点与SimpleTrigger一样,只是可以通过表达式来设置时间而已。

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

第六章 SpringBoot整合Quartz

6.1 节 SpringBoot整合Quartz

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相关配置,简单配置,更多的配置可以到官网查看。

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

/**
 * @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);
    }
}

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

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

单线程运行任务不同任务之间串行,任务A运行时间会响应任务B运行间隔,这是我们不想看到的。而多线程执行任务调度不同任务之间的运行间隔不会相互影响。

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

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

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

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

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

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

  3. 数据共享: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.coloradmin.cn/o/1382738.html

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

相关文章

Linux系统——远程访问及控制

目录 一、OpenSSH服务器 1.SSH&#xff08;Secure Shell&#xff09;协议 2.OpenSSH 2.SSH原理 2.1公钥传输原理 2.2加密原理 &#xff08;1&#xff09;对称加密 &#xff08;2&#xff09;非对称加密 2.3远程登录 2.3.1延伸 2.3.2登录用户 3.SSH格式及选项 3.1延…

K8s-Pod资源(一)Pod介绍、创建Pod、Pod简单资源配额

Pod概述 Kubernetes Pod | Kubernetes Pod是Kubernetes中的最小调度单元&#xff0c;k8s都是以pod的方式运行服务的 一个pod可以指定镜像&#xff0c;封装一个或多个容器 pod需要调度到工作节点运行&#xff0c;节点的选择由scheduler调度器实现 pod定义时&#xff0c;会…

【Python学习】Python学习17- File(文件) 方法

目录 [TOC](【Python学习】Python学习17- File(文件) 方法) 文章所属专区 Python学习 前言 本章节主要说明Python文件操作的具体说明 open()方法 Python open() 方法用于打开一个文件&#xff0c;并返回文件对象&#xff0c;在对文件进行处理过程都需要使用到这个函数&#…

C++ QtCreator启动执行报错的各类问题解决++持续更新!!

1.QTCreator启动报错"由于找不到 python310.dll" 在QtCreator加载自动缩进的LLVM插件后, 再次打开Qt时, 会报错找不到python310.dll 解决方法&#xff1a;下载python310.dll,随后复制到目录&#xff1a;C:\Program Files\LLVM\bin 即可解决该问题。下载路径附件如…

TensorRT(C++)基础代码解析

TensorRT(C)基础代码解析 文章目录 TensorRT(C)基础代码解析前言一、TensorRT工作流程二、C API2.1 构建阶段2.1.1 创建builder2.1.2 创建网络定义2.1.3 定义网络结构2.1.4 定义网络输入输出2.1.5 配置参数2.1.6 生成Engine2.1.7 保存为模型文件2.1.8 释放资源 2.2 运行期2.2.1…

C#--核心

CSharp核心知识点学习 学习内容有&#xff1a; 绪论&#xff1a;面向对象的概念 Lesson1&#xff1a;类和对象 练习&#xff1a; Lesson2&#xff1a;封装--成员变量和访问修饰符 练习: Lesson3:封装--成员方法 Lesson4&#xff1a;封装--构造函数和析构函数 知识点四 垃圾回收…

Web前端-移动web开发——flex布局

移动web开发——flex布局 1.0传统布局和flex布局对比 1.1传统布局 兼容性好布局繁琐局限性&#xff0c;不能再移动端很好的布局 1.2 flex布局 操作方便&#xff0c;布局极其简单&#xff0c;移动端使用比较广泛pc端浏览器支持情况比较差IE11或更低版本不支持flex或仅支持部…

SouthernBiotech抗荧光淬灭封片剂

荧光淬灭又称荧光熄灭或萃灭&#xff0c;是指导致特定物质的荧光强度和寿命减少的所有现象。引起荧光淬灭的物质称为荧光淬灭剂。SouthernBiotech专门开发的Fluoromount-G系列荧光封片剂是以甘油为基础&#xff0c;加入抗荧光淬灭剂&#xff0c;可明显降低荧光淬灭现象&#xf…

使用setdefault撰写文本索引脚本(出自Fluent Python案例)

背景介绍 由于我们主要介绍撰写脚本的方法&#xff0c;所以用一个简单的文本例子进行分析 a[(19,18),(20,53)] Although[(11,1),(16,1),(18,1)] ambiguity[(14,16)] 以上内容可以保存在一个txt文件中&#xff0c;任务是统计文件中每一个词&#xff08;包括字母&#xff0c;数…

AI编程可视化Java项目拆解第一弹,解析本地Java项目

之前分享过一篇使用 AI 可视化 Java 项目的文章&#xff0c;同步在 AI 破局星球、知乎、掘金等地方都分享了。 原文在这里AI 编程&#xff1a;可视化 Java 项目 有很多人感兴趣&#xff0c;我打算写一个系列文章拆解这个项目&#xff0c;大家多多点赞支持~ 今天分享的是第一…

C语言之从浅入深一步一步全方位理解指针【附笔试题】

文章目录 前言从浅入深理解指针《第一阶段》一、内存和地址1.1 内存1.2 究竟该如何理解编址 二、指针变量和地址2.1 取地址操作符&#xff08;&&#xff09; 三、指针变量和解引用操作符&#xff08;*&#xff09;3.1 指针变量3.2 如何拆解指针类型3.3 解引用操作符 四、指…

OpenCV-Python的版本介绍及区别

OpenCV-Python版本介绍 OpenCV-Python有多个版本&#xff0c;每个版本都有其特定的功能和改进。以下是一些常见OpenCV-Python版本及其介绍和区别&#xff1a; OpenCV-Python 2.x版本 这是OpenCV-Python的旧版本&#xff0c;支持Python 2.x。它包含了许多传统的计算机视觉功能&…

rpc的正确打开方式|读懂Go原生net/rpc包

前言 大家好&#xff0c;这里是白泽&#xff0c;之前最近在阅读字节跳动开源RPC框架Kitex的源码&#xff0c;分析了如何借助命令行&#xff0c;由一个IDL文件&#xff0c;生成client和server的脚手架代码&#xff0c;也分析了Kitex的日志组件klog。当然Kitex还有许多其他组件&…

sqlilabs第五十三五十四关

Less-51(GET - GET - Error based - ORDER BY CLAUSE-String- Stacked injection) 手工注入 单引号闭合&#xff0c;和上一关一样堆叠注入解决 自动注入 和上一关一样 Less-52(GET - challenge - Union- 10 queries allowed -Variation 1) 手工注入 这一关开始后面的可以看…

Linux 内核如何根据设备树文件来匹配内核

一. 简介 上一篇文章学习了 Linux内核如何确定是否支持此设备&#xff0c;如果支持&#xff0c;设备就会启动 Linux 内核。 文章地址如下&#xff1a; 设备树根节点下的compatile属性的作用-CSDN博客 本文继上面文章的学习。这里简单看一下&#xff0c; Linux 内核是如何根…

学习资料: uni-app HBuilderX

编译器&#xff1a;HBuilderX HBuilderX-高效极客技巧 uni-app介绍&#xff1a;uni-app官网 uni-app 是一个使用 Vue.js 开发所有前端应用的框架&#xff0c;开发者编写一套代码&#xff0c;可发布到iOS、Android、Web&#xff08;响应式&#xff09;、以及各种小程序&#…

2023.1.13 关于在 Spring 中操作 Redis 服务器

目录 引言 前置工作 前置知识 实例演示 String 类型 List 类型 Set 类型 Hash 类型 ZSet 类型 引言 进行下述操作的前提是 你的云服务器已经配置好了 ssh 端口转发即已经将云服务器的 Redis 端口映射到本地主机 注意&#xff1a; 此处我们配置的端口号为 8888 可点击下…

C++力扣题目617--合并二叉树

给你两棵二叉树&#xff1a; root1 和 root2 。 想象一下&#xff0c;当你将其中一棵覆盖到另一棵之上时&#xff0c;两棵树上的一些节点将会重叠&#xff08;而另一些不会&#xff09;。你需要将这两棵树合并成一棵新二叉树。合并的规则是&#xff1a;如果两个节点重叠&#…

【STM32】FLASH闪存

1 FLASH闪存简介 本节所指STM32内部闪存&#xff0c;即下载程序的时候&#xff0c;程序存储的地方。&#xff08;非易失性&#xff09; STM32F1系列的FLASH包含程序存储器、系统存储器&#xff08;bootloader&#xff0c;不允许修改&#xff09;和选项字节三个部分&#xff0…

JavaScript学习笔记——变量、作用域、var、const和let

JavaScript学习笔记——变量、作用域、var、const和let 学习链接&#xff08;原链接&#xff09;变量变量声明的三种方式 作用域作用域介绍作用域分类全局作用域局部作用域&#xff08;函数作用域&#xff09;块级作用域块级作用域和局部(函数)作用域区别 varvar的作用域(全局函…