spring boot 2.x 深度理解定时任务 schedule

2,383 阅读5分钟

这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战

先看 spring boot 2.x actuator 监控/健康检查/审计/统计

基于上述代码修改

源码:github.com/langyastudi…

官方文档: docs.spring.io/spring-fram…

在很多应用程序中经常需要执行定时任务。例如每天给用户发送报表,定时检查系统状态等。在 Spring + SpringMVC 环境中,一般来说要实现定时任务有两种方案:

  • 一种是使用 Spring 自带的定时任务处理器 @Scheduled 注解
  • 另一种是使用第三方框架如 Quartz

Spring 自带方案

Spring 内置定时任务和 Cron 任务的支持,因此不需要手写大量代码,只需要使用 2 个注解即可:

  • 通过 @EnableScheduling 开启对计划任务的支持(基于类注解)

  • 使用 @Scheduled 注解需要计划执行的任务(基于函数注解)

Scheduled

具体步骤:

添加 @EnableScheduling 开启定时任务的支持:

@SpringBootApplication
@EnableScheduling
public class Application
{
    public static void main(String[] args)
    {
       ...
    }
}

接下来定义计划任务的类(标记 @Component)并使用 @Scheduled 注解计划执行的任务:

@Log4j2
@Component
public class SchedulingService
{
    @Scheduled(fixedRate = 60000)
    public void  fixedRateSchedule()
    {
        log.info("每 60 秒钟执行fixedRateSchedule...");
    }


    /**
     * initialDelay 延迟 5 秒后开始执行该任务
     */
    @Scheduled(initialDelay = 5000, fixedDelay = 10000)
    public void  fixedDelaySchedule()
    {
        log.info("在上次任务完成 10 秒后执行fixedDelaySchedule...");
    }
}

重点参数说明:

  • fixedRate

    每隔固定的时间执行一次,无论上次任务是否完成

  • fixedDelay

    在上次任务执行完成后,在指定时间后执行新任务

  • initialDelay

    表示首次任务启动的延迟时间

  • 所有时间的单位都是毫秒

例如上述的 fixedDelaySchedule 函数的注解指定了启动延迟 5 秒,并以 10 秒的间隔执行任务。可以在控制台看到定时任务打印的日志:

2021-08-16 18:30:14.729 INFO [scheduling-1] com.langyastudio.springboot.common.middleware.task.SchedulingService : 在上次任务完成 10 秒后执行fixedDelay...
2021-08-16 18:30:24.730 INFO [scheduling-1] com.langyastudio.springboot.common.middleware.task.SchedulingService : 在上次任务完成 10 秒后执行fixedDelay...
2021-08-16 18:30:34.732 INFO [scheduling-1] com.langyastudio.springboot.common.middleware.task.SchedulingService : 在上次任务完成 10 秒后执行fixedDelay...

动态修改参数值:

例如 fixedDelay=10000,如果根据实际情况要改成 60 秒怎么办,难道只能重新编译?

可以看到 Scheduled 注解提供了 fixedDelayStringfixedRateStringinitialDelayString 等参数,可以把定时任务的配置值放到配置文件中,从而达到动态修改计划任务时间的目的。

例如定义配置文件:

langyastudio:
  task:
    fixedDelaySchedule: 60000

再通过 xxxString 替代 xxx 的方式实现动态修改参数值的目的:

@Scheduled(initialDelay = 5000, fixedDelayString = "${langyastudio.task.fixedDelaySchedule:10000}")
public void  fixedDelaySchedule()
{
    log.info("在上次任务完成 10 秒后执行fixedDelay...");
}

上述的 fixedDelayString ,表示先从配置文件中获取 langyastudio.task.fixedDelaySchedule 值,如果没有则使用默认值 10000。

计划任务配置:

示例如下:

spring:
  task:
    scheduling:
      pool:
        size: 5        
      shutdown:
        #关闭应用程序时,是否等待任务执行完毕
        await-termination: true
        await-termination-period: 60000 

通过配置参数,可以定制计划任务在应用关闭时采用的措施,如 await-termination 表示等待执行完毕再关闭

Cron

Cron 源自 Unix/Linux 系统自带的 crond 守护进程,以一个简洁的表达式定义任务触发时间(按时间触发,不同于重复计划任务)。

推荐一个在线 Cron 表达式生成器: crontab.guru/

在 Spring 中可以使用 Cron 表达式来执行 Cron 任务,它的格式如下:

 ┌───────────── second (0-59)
 │ ┌───────────── minute (0 - 59)
 │ │ ┌───────────── hour (0 - 23)
 │ │ │ ┌───────────── day of the month (1 - 31)
 │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
 │ │ │ │ │ ┌───────────── day of the week (0 - 7)
 │ │ │ │ │ │          (0 or 7 is Sunday, or MON-SUN)
 │ │ │ │ │ │
 * * * * * *
  秒 分 小时 天 月份 星期 年 

具体取值如下:

序号说明是否必填允许填写的值允许的通配符
10-59- * /
20-59- * /
30-23- * /
41-31- * ? / L W
51-12 or JAN-DEC- * /
61-7 or SUN-SAT- * ? / L #
71970-2099- * /

示例:

周一至周五的 12:00 执行的 Cron 表达式:

0 0 12 * * MON-FRI

每个月 1-3 号和 10 号 12:00 执行的 Cron 表达式:

0 0 12 1-3,10 * *

在 spring 中定义一个每 30 秒执行的任务:

@Scheduled(cron = "*/30 * * * * *")
public void  cronchedule()
{
    log.info("每30秒执行cron...");
}

此时 xx:xx:00xx:xx:30 执行上述任务

cron 也可以使用配置文件,不再详述

Quartz

在 Spring 中使用定时任务和 Cron 任务都十分简单,但是要注意到,这些任务的调度都是在每个 JVM 进程中的。如果在本机启动两个进程或者在多台机器上启动应用,这些进程的定时任务和 Cron 任务都是独立运行的,互不影响。

如果一些定时任务要以集群的方式运行,例如每天 23:00 执行检查任务,只需要集群中的一台运行即可,这个时候可以考虑使用 Quartz

Quartz 可以配置一个 JDBC 数据源,以便存储所有的任务调度计划以及任务执行状态。详细了解Quartz的集成,请参考 Spring的文档