• 【任务调度】定时任务,SpringTask,Quartz


    在 JDK 中,内置了两个类,可以实现定时任务的功能:

    • java.util.Timer :可以通过创建 java.util.TimerTask 调度任务,在同一个线程中串行执行,相互影响。也就是说,对于同一个 Timer 里的多个 TimerTask 任务,如果一个 TimerTask 任务在执行中,其它 TimerTask 即使到达执行的时间,也只能排队等待。因为 Timer 是串行的,同时存在 坑坑 ,所以后来 JDK 又推出了 ScheduledExecutorService ,Timer 也基本不再使用。
    • java.util.concurrent.ScheduledExecutorService :在 JDK 1.5 新增,基于线程池设计的定时任务类,每个调度任务都会被分配到线程池中并发执行,互不影响。这样,ScheduledExecutorService 就解决了 Timer 串行的问题。

    但是它们仅支持按照指定频率,不直接支持指定时间的定时调度,需要结合Calendar自行计算,才能实现复杂时间的调度。
    它们是进程级别,而我们为了实现定时任务的高可用,需要部署多个进程。此时需要多考虑,多个进程下,同一个任务在相同时刻,不能重复执行
    项目可能存在定时任务较多,需要统一的管理,此时不得不进行二次封装
    所以我们可以用调度任务中间件

    在 Spring 体系中,内置了两种定时任务的解决方案:

    • 第一种,Spring Framework 的 Spring Task 模块,提供了轻量级的定时任务的实现。
    • 第二种,Spring Boot 2.0 版本,整合了 Quartz 作业调度框架,提供了功能强大的定时任务的实现。
      注:Spring Framework 已经内置了 Quartz 的整合。Spring Boot 1.X 版本未提供 Quartz 的自动化配置,而 2.X 版本提供了支持。

    Spring Task

    • 创建配置类,启动定时任务
    // ScheduleConfiguration.java
    
    @Configuration
    @EnableScheduling
    public class ScheduleConfiguration {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在类上,通过添加 @EnableScheduling 注解,启动 Spring Task 的定时任务调度的功能。

    • 创建任务类
    @Component
    public class TaskDemo {
    
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        private final AtomicInteger counts = new AtomicInteger();
    
        @Scheduled(fixedRate = 2000)
        public void execute() {
            logger.info("[execute][定时第 ({}) 次执行]", counts.incrementAndGet());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    其中的 #execute() 方法用于实现任务要执行的内容,这里是打印一下日志。
    同时,在该方法上,添加 @Scheduled 注解,设置每 2 秒执行该方法。

    @Configuration
    @EnableScheduling//开启定时任务
    @EnableAsync//开启多线程
    pubic class config{
        @Scheduled(cron="0 0 23 * * ?")//每天23点执行
        @Async//异步
        public void method(){}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • @Scheduled常用属性

      @Scheduled 注解用于设置定时任务的执行计划。

      常用属性如下:

      • cron 属性:Spring Cron 表达式。例如说,“0 0 12 * * ?” 表示每天中午执行一次,“11 11 11 11 11 ?” 表示 11 月 11 号 11 点 11 分 11 秒执行一次。注意,以调用完成时刻为开始计时时间。
      • fixedDelay 属性:固定执行间隔,单位:毫秒。注意,以调用完成时刻为开始计时时间。
      • 控制方法执行的间隔时间,是以上一次方法执行完开始算起,如上一次方法执行阻塞住了,那么直到上一次执行完,并间隔给定的时间后,执行下一次。cron 和此方式类似。
      • fixedRate 属性:固定执行间隔,单位:毫秒。注意,以调用开始时刻为开始计时时间。
      • 按照一定的速率执行,是从上一次方法执行开始的时间算起,如果上一次方法阻塞住了,下一次也是不会执行,但是在阻塞这段时间内累计应该执行的次数,当不再阻塞时,一下子把这些全部执行掉,而后再按照固定速率继续执行。

      注意三者区别。

      不常用属性如下:

      • initialDelay 属性:初始化的定时任务执行延迟,单位:毫秒。
      • zone 属性:解析 Spring Cron 表达式的所属的时区。默认情况下,使用服务器的本地时区。
      • initialDelayString 属性:initialDelay 的字符串形式。
      • fixedDelayString 属性:fixedDelay 的字符串形式。
      • fixedRateString 属性:fixedRate 的字符串形式。
    • Spring Task配置

      在 application.yml 中,添加 Spring Task 定时任务的配置,如下:

    spring:
      task:
        # Spring Task 调度任务的配置,对应 TaskSchedulingProperties 配置类
        scheduling:
          thread-name-prefix: sh-demo- # 线程池的线程名的前缀。默认为 scheduling- ,建议根据自己应用来设置
          pool:
            size: 10 # 线程池大小。默认为 1 ,根据自己应用来设置
          shutdown:
            await-termination: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
            await-termination-period: 60 # 等待任务完成的最大时长,单位为秒。默认为 0 ,根据自己应用来设置
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 在 spring.task.scheduling 配置项中,Spring Task 调度任务的配置对应 TaskSchedulingProperties 配置类。
    • Spring Boot TaskSchedulingAutoConfiguration 自动化配置类,实现 Spring Task 的自动配置,默认创建 ThreadPoolTaskScheduler 基于线程池的任务调度器。
    • 本质上,ThreadPoolTaskScheduler 是基于 ScheduledExecutorService 的封装,增强在调度时间上的功能。
    • 通过配置 await-termination = true ,实现应用关闭时,等待定时任务执行完成。这样,应用在关闭的时,Spring 会优先等待 ThreadPoolTaskScheduler 执行完任务之后,再开始 Spring Bean 的销毁。
    • 同时,又考虑到我们不可能无限等待定时任务全部执行结束,因此可以配置 await-termination-period = 60 ,等待任务完成的最大时长,单位为秒。具体设置多少的等待时长,可以根据自己应用的需要。

    注意,spring.task.scheduling.shutdown 配置项,是为了实现 Spring Task 定时任务的优雅关闭。
    我们想象一下,如果定时任务在执行的过程中,如果应用开始关闭,把定时任务需要使用到的 Spring Bean 进行销毁
    例如说数据库连接池,那么此时定时任务还在执行中,一旦需要访问数据库,可能会导致报错。

    Quartz

    在这里插入图片描述
    在 Quartz 体系结构中,有三个组件非常重要,它们是 Job,Trigger 和 Scheduler:

    • Job: 任务或作业
      • Job 是一个接口,只定义一个方法 execute (JobExecutionContext context),在实现接口的 execute 方法中编写所需要定时执行的 Job (任务)。
      • JobExecutionContext 类提供了调度应用的一些信息。
      • Job 运行时的信息保存在 JobDataMap 实例中。
        • Job 有一个 StatefulJob 子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让 Quartz 知道任务的类型,以便采用不同的执行方案。
        • 无状态任务在执行时拥有自己的 JobDataMap 拷贝,对 JobDataMap 的更改不会影响下次的执行。
        • 有状态任务则共享同一个 JobDataMap 实例,每次任务执行对 JobDataMap 所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执行任务后都会对后面的执行发生影响。
        • 正因为这个原因,无状态的 Job 可以并发执行,而有状态的 StatefulJob 不能并发执行,这意味着如果前次的 StatefulJob 还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。
        • 有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的复杂度,因此除非必要,应该尽量使用无状态的 Job。
      • 如果 Quartz 使用了数据库持久化任务调度信息,无状态的 JobDataMap 仅会在 Scheduler 注册任务时保持一次,而有状态任务对应的 JobDataMap 在每次执行任务后都会进行保存。
    • JobDetail: Job 的实现类
      • JobDetail 描述了 Job 的实现类及其它相关的静态信息,如 Job 名字、描述、关联监听器等信息),以便运行时通过 newInstance () 的反射机制实例化 Job。
      • Quartz 每次调度 Job 时, 都重新创建一个 Job 实例, 所以它不直接接受一个 Job 的实例,相反它接收一个 Job 实现类。
      • 可通过 JobBuilder.newJob() 构建。
    • Trigger:触发器
      • 描述触发 Job 执行的时间触发规则。主要使用 SimpleTrigger 和 CronTrigger 这两个子类。
      • 当且仅当需调度一次或者以固定时间间隔周期执行调度,SimpleTrigger 是最适合的选择。
      • CronTrigger 则可以通过 Cron 表达式定义出各种复杂时间规则的调度方案:如工作日周一到周五的 15:00~16:00 执行调度等;
      • Trigger 自身也可以拥有一个 JobDataMap,其关联的 Job 可以通过 JobExecutionContext#getTrigger ().getJobDataMap () 获取 Trigger 中的 JobDataMap。
      • 不管是有状态还是无状态的任务,在任务执行期间对 Trigger 的 JobDataMap 所做的更改都不会进行持久,也即不会对下次的执行产生影响。
      • Quartz 拥有完善的事件和监听体系,大部分组件都拥有事件,如任务执行前事件、任务执行后事件、触发器触发前事件、触发后事件、调度器开始事件、关闭事件等等,可以注册相应的监听器处理感兴趣的事件。
      • 可通过 TriggerBuilder.newTrigger() 构建。
    • Scheduler :调度器
      • 代表一个 Quartz 的独立运行容器, Trigger 和 JobDetail 可以注册到 Scheduler 中, 两者在 Scheduler 中拥有各自的组及名称, 组及名称是 Scheduler 查找定位容器中某一对象的依据, Trigger 的组及名称必须唯一, JobDetail 的组和名称也必须唯一(但可以和 Trigger 的组和名称相同,因为它们是不同类型的)。
      • Scheduler 定义了多个接口方法, 允许外部通过组及名称访问和控制容器中 Trigger 和 JobDetail。
      • Scheduler 可以将 Trigger 绑定到某一 JobDetail 中, 这样当 Trigger 触发时, 对应的 Job 就被执行。
      • 一个 Job 可以对应多个 Trigger, 但一个 Trigger 只能对应一个 Job。
      • 可以通过 SchedulerFactory 创建一个 Scheduler 实例。
      • Scheduler 拥有一个 SchedulerContext,它类似于 ServletContext,保存着 Scheduler 上下文信息,Job 和 Trigger 都可以访问 SchedulerContext 内的信息。
      • SchedulerContext 内部通过一个 Map,以键值对的方式维护这些上下文数据,SchedulerContext 为保存和获取数据提供了多个 put () 和 getXxx () 的方法。
      • 可以通过 Scheduler# getContext () 获取对应的 SchedulerContext 实例;

    执行任务类继承Job,实现execute方法

    • 创建调度器Scheduler
    SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    Scheduler scheduler = schedulerFactory.getScheduler();
    
    • 1
    • 2
    • 创建JobDetail实例,并于Job类绑定
     JobDetail jobDetail = JobBuilder.newJob(TestJob.class)
                    .withIdentity("job1","group1")//任务名
                    .build();
    
    • 1
    • 2
    • 3
    • 构建Trigger实例,并设定隔多少时间执行一次
    Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("tigger1","tiggerGroup1")
                    .startNow()//立即生效
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                            .withIntervalInSeconds(1)//每隔1s执行一次
                            .repeatForever())//一直执行
                    .build();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 执行
    scheduler.scheduleJob(jobDetail,trigger);
    scheduler.start();
    
    • 1
    • 2

    Demo

    引入依赖:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4

    创建Job

    public class HelloQuartz implements Job {
    
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            System.out.println("Hello Quartz!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    调用Job

    import org.quartz.*;
    import org.quartz.impl.StdSchedulerFactory;
    
    public class HelloQuartzApplication {
        public static void main(String[] args) throws SchedulerException, InterruptedException {
    
            // 1. 通过 SchedulerFactory 获取一个调度器
            StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
            Scheduler scheduler = stdSchedulerFactory.getScheduler();
    
            // 2. 创建jobDetail实例,绑定Job实现类
            JobDetail job = JobBuilder.newJob(HelloQuartz.class).withIdentity("HelloJob", "HelloJobGroup").build();
    
            // 3. 创建触发器
            //        // SimpleTrggier,定义调度触发规则
            //        SimpleTrigger trigger1 = TriggerBuilder
            //                .newTrigger().withIdentity("HelloSimpleTrigger", "HelloSimpleTriggerGroup")
            //                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(3).withRepeatCount(6))
            //                .startNow().build();
            //        // 把作业和触发器注册到任务调度中
            //        scheduler.scheduleJob(job, trigger1);
    
            //  CronTrigger,corn表达式:每五秒执行一次
            Trigger trigger2 =TriggerBuilder.newTrigger().withIdentity("HelloCronTrigger", "HelloCronTriggerGroup")
                .withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?"))
                .startNow().build();
            scheduler.scheduleJob(job, trigger2);
    
            // 4. 启动调度
            scheduler.start();
    
            Thread.sleep(18000);
    
            // 5. 停止调度
            scheduler.shutdown();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
  • 相关阅读:
    机器学习1综述
    Web 3.0 是泡沫还是金矿?
    MySQL的事务和引擎存储
    信号包络提取的方法
    Go runtime 调度器精讲(十):异步抢占
    chapter4——时钟分频器
    拓展卡尔曼滤波(Kalman)附Matlab代码
    Stata绘制分类带可信区间的折线图
    基于Python实现一个庆祝中秋节的小程序
    1990-2021年上市公司债务融资成本数据(原始数据+stata处理代码+计算结果)
  • 原文地址:https://blog.csdn.net/m0_57293204/article/details/127839372