⏲️定时任务(1):Spring Task
00 分钟
2024-7-25
2024-7-26
type
status
date
Jul 26, 2024 04:03 AM
slug
summary
category
tags
password
icon

背景

在色彩斑斓的产品需求中,有存在一类需求,是需要去定时执行的,此时就需要使用到定时任务。例如说,每分钟扫描超时支付的订单,每小时清理一次日志文件,每天统计前一天的数据并生成报表,每个月月初的工资单的推送,每年一次的生日提醒等等。
在 JDK 中,内置了两个类,可以实现定时任务的功能:
  • java.util.Timer :可以通过创建 java.util.TimerTask 调度任务,在同一个线程中串行执行,相互影响。也就是说,对于同一个 Timer 里的多个 TimerTask 任务,如果一个 TimerTask 任务在执行中,其它 TimerTask 即使到达执行的时间,也只能排队等待。因为 Timer 是串行的,同时存在  ,所以后来 JDK 又推出了 ScheduledExecutorService ,Timer 也基本不再使用。
  • java.util.concurrent.ScheduledExecutorService :在 JDK 1.5 新增,基于线程池设计的定时任务类,每个调度任务都会被分配到线程池中并发执行,互不影响。这样,ScheduledExecutorService 就解决了 Timer 串行的问题。
在日常开发中,我们很少直接使用 Timer 或 ScheduledExecutorService 来实现定时任务的需求。主要有几点原因:
  • 它们仅支持按照指定频率,不直接支持指定时间的定时调度,需要我们结合 Calendar 自行计算,才能实现复杂时间的调度。例如说,每天、每周五、2019-11-11 等等。
  • 它们是进程级别,而我们为了实现定时任务的高可用,需要部署多个进程。此时需要等多考虑,多个进程下,同一个任务在相同时刻,不能重复执行。
  • 项目可能存在定时任务较多,需要统一的管理,此时不得不进行二次封装。
所以,一般情况下,我们会选择专业的调度任务中间件
关于“任务”的叫法,也有叫“作业”的。在英文上,有 Task 也有 Job 。本质是一样的,本文两种都会用。
然后,一般来说是调度任务,定时执行。所以胖友会在本文,或者其它文章中,会看到“调度”或“定时”的字眼儿。
在 Spring 体系中,内置了两种定时任务的解决方案:
  • 第二种,Spring Boot 2.0 版本,整合了 Quartz 作业调度框架,提供了功能强大的定时任务的实现。
    • 注:Spring Framework 已经内置了 Quartz 的整合。Spring Boot 1.X 版本未提供 Quartz 的自动化配置,而 2.X 版本提供了支持。
在 Java 生态中,还有非常多优秀的开源的调度任务中间件:
  • Elastic-Job
    • 唯品会基于 Elastic-Job 之上,演化出了 Saturn 项目。
目前国内采用 Elastic-Job 和 XXL-JOB 为主。在实际应用开发中,使用 XXL-JOB 的团队会更多一些,主要是上手较为容易,运维功能更为完善。

Spring Task基本使用

引入Maven依赖

ScheduleConfiguration

创建 ScheduleConfiguration 类,配置 Spring Task

DemoJob

  • 在类上,添加 @Component 注解,创建 DemoJob Bean 对象。
  • 创建 #execute() 方法,实现打印日志。同时,在该方法上,添加 @Scheduled 注解,设置每 2 秒执行该方法。

Application启动类

在 Spring Boot 应用的启动类中,确保添加 @SpringBootApplication 注解,并运行应用。

测试

运行 Application 类,启动示例项目。输出日志如下:

相关配置

@Scheduled

  1. 常用属性如下:
      • cron 属性:Spring Cron 表达式。例如说,"0 0 12 * * ?" 表示每天中午执行一次,"11 11 11 11 11 ?" 表示 11 月 11 号 11 点 11 分 11 秒执行一次。更多示例和讲解,可以看看 Spring Cron 表达式,也可以使用Quartz/Cron/Crontab 表达式在线生成工具 。注意,以调用完成时刻为开始计时时间。
      • fixedDelay 属性:固定执行间隔,单位:毫秒(ms)。注意,以调用完成时刻为开始计时时间。也就是说,从上一个方法执行完成开始算起,如果上一次方法执行阻塞住了,那么直到上一次执行完,并间隔给定的时间后,才执行下一次(即每次执行都会间隔一段时间)
      • fixedRate 属性:固定执行间隔,单位:毫秒(ms)。注意,以调用开始时刻为开始计时时间。也就是说,从上一次方法执行开始的时间算起,如果上一次方法阻塞住了,下一次也是不会执行,但是在阻塞这段时间内累计应该执行的次数,当不再阻塞时,一下子把这些全部执行掉,而后再按照固定速率继续执行(即不会让下次执行一定要在上一次执行完之后间隔一段时间才执行)。
  1. 不常用属性如下:
      • initialDelay 属性:初始化的定时任务执行延迟,单位:毫秒(ms)。
      • zone 属性:解析 Spring Cron 表达式的所属的时区。默认情况下,使用服务器的本地时区。
      • initialDelayString 属性:initialDelay 的字符串形式。
      • fixedDelayString 属性:fixedDelay 的字符串形式。
      • fixedRateString 属性:fixedRate 的字符串形式。

配置文件

  • Spring Boot TaskSchedulingAutoConfiguration 自动化配置类,实现 Spring Task 的自动配置,创建 ThreadPoolTaskScheduler 基于线程池的任务调度器。本质上,ThreadPoolTaskScheduler 是基于 ScheduledExecutorService 的封装,增强在调度时间上的功能。
注意spring.task.scheduling.shutdown 配置项,是为了实现 Spring Task 定时任务的优雅关闭。我们想象一下,如果定时任务在执行的过程中,如果应用开始关闭,把定时任务需要使用到的 Spring Bean 进行销毁,例如说数据库连接池,那么此时定时任务还在执行中,一旦需要访问数据库,可能会导致报错。
  • 所以,通过配置 await-termination = true ,实现应用关闭时,等待定时任务执行完成。这样,应用在关闭的时,Spring 会优先等待 ThreadPoolTaskScheduler 执行完任务之后,再开始 Spring Bean 的销毁。
  • 同时,又考虑到我们不可能无限等待定时任务全部执行结束,因此可以配置 await-termination-period = 60 ,等待任务完成的最大时长,单位为秒。具体设置多少的等待时长,可以根据自己应用的需要。

评论
Loading...