SpringBoot使用@Schedueld实现定时任务以及优化

简单介绍 @Scheduled 是 Spring 框架提供的一个注解,用于在 Spring 管理的 Bean 中定义计划任务。它允许开发者在 Spring Boot 应用中执行周期性的后台任务,而无需使用外部任务调度器。 一.@Scheduled注解参数 1.cron 这个参数是最经常使用的参数,表

简单介绍

@Scheduled 是 Spring 框架提供的一个注解,用于在 Spring 管理的 Bean 中定义计划任务。它允许开发者在 Spring Boot 应用中执行周期性的后台任务,而无需使用外部任务调度器。

一.@Scheduled注解参数

1.cron

这个参数是最经常使用的参数,表示接收一个cron参数,cron它是一个表达式,最多接收7个参数,从左到右分别表示:秒 分 时 天 月 周 年;参数以空格隔开,其中年不是必须参数,可以省略。

/**
* cron 一共可以有7个参数 以空格分开 其中年不是必须参数
* [秒] [分] [小时] [日] [月] [周] [年]
* 一下表示
*/
@Scheduled(cron ="0 0 0 * * * ?")
public void testScheduledCron(){
}

参数含义:

序号

含义

是否必填

范围

通配符

1

0-59

, - * /

2

0-59

, - * /

3

0-23

, - * /

4

1-31

, - * ? / L W

5

1-12

, - * /

6

星期

1-7 

, - * ? / L #

7

1970-2099

, - * /

常用通配符:

*:表示所有值 比如用在日 表示每一天。

?:表示不指定值 比如周配置 表示不指定星期几执行。

/:表示递增触发 比如 用在分 5/20 从第五分钟开始 每增加20分钟执行一次。

-:表示区间 比如用在 1-6 表示一月到六月执行。

示例:

每天凌晨零点执行

@Scheduled(cron ="0 0 0 * * * ?")

每隔五分钟执行

@Scheduled(cron ="0 */5 0 * * * ?")

2.zone

zone 能够指定获取的时区,默认是空,表示使用服务器所在时区,比如Asia/BeiJingi或者Asia/Shanghai

3.fixedDelay

fixedDelay表示上次调用结束后与下次调用之间的固定时间,单位是毫秒。

@Scheduled(fixedDelay= 3000)

4.fixedDelayString

fixedDelayString与fixedDelay是几乎一样的,唯一的差异是fixedDelayString是支持占位符的。

5.fiexdRate

固定速率

上一次任务执行开始到下一次执行开始的间隔时间固定,单位为ms。与cron的/通配符用法相似

若在调度任务执行时,上一次任务还未执行完毕,会加入worker队列,等待上一次执行完成后,马上执行下一次任务

示例:每三秒执行一次 @Scheduled(fixedRate= 3000)

6.fixedRateString

与fixedRate一致,只是间隔时间使用java.time.Duration#parse解析

7.initialDelay

首次延迟多长时间后执行,单位ms。

之后按照fixedRate、fixedRateString、fixedDelay、fixedDelayString指定的规则执行,需要指定其中一个规则。

:不能和cron一起使用

示例:表示第一次执行时,延迟3秒执行 @Scheduled(initialDelay= 3000)

8.initialDelayString

initialDelay的升级,支持占位符

二、使用@Scheduled

基础使用

使用 @Scheduled 需要在 Spring Boot 应用中启用任务调度功能。首先,在配置类上添加 @EnableScheduling 注解:

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling
public class ScheduledConfig {
}

然后在任何被 Spring 管理的 Bean 中,定义一个方法并使用 @Scheduled 注解来指定任务的执行规则

进阶使用

并发控制

当定时任务有可能并发执行时,需要确保任务的原子性。可以使用Spring的 @Lock 注解配合 @Scheduled 来防止并发执行:

// 非并发执行的任务,使用锁确保同一时间只有一个实例在执行
@Scheduled(fixedRate = 5000)
@Lock("uniqueTaskLock")
public void nonConcurrentTask() {
    System.out.println("Executing non-concurrent task...");
}

动态调度

有时我们需要根据外部条件动态更改任务的调度策略,可以通过注入 TaskScheduler 接口来实现:

import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;

// ...

@Autowired
private TaskScheduler scheduler;

public void scheduleTaskAtFixedRate(Runnable task, long period) {
    scheduler.schedule(task, new CronTrigger("*/" + (period / 1000) + " * * * * ?"));
}

public void cancelScheduledTask(ScheduledFuture<?> future) {
    if (future != null) {
        future.cancel(true);
    }
}

复杂的Cron表达式

Cron表达式提供了丰富的语法来定义复杂的调度规则,例如结合日期、时间甚至是月份来精确控制任务执行时间:

// 在每个月的第一天晚上10点执行
@Scheduled(cron = "0 22 1 * * ?")
public void monthlyTask() {
    System.out.println("Monthly task executed at 10 PM on the first day of each month.");
}

外部化配置

将Cron表达式或其他调度参数外部化到配置文件中,使得无需重新编译代码即可修改任务的执行策略:

scheduled.dynamic.cron=0 0 2 * * ?

然后在 Java 代码中读取这些配置:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledTasks {

    @Value("${scheduled.dynamic.cron}")
    private String dynamicCron;

    @Scheduled(cron = "${scheduled.dynamic.cron}")
    public void dynamicTask() {
        System.out.println("Executing dynamically configured task...");
    }
}

三、案例

日志记录

确保所有定时任务都有详细的日志记录,特别是在发生异常时,便于后续排查问题。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledTasks {

    private static final Logger logger = LoggerFactory.getLogger(ScheduledTasks.class);

    @Scheduled(fixedRate = 5000)
    public void repeatTask() {
        try {
            // 执行任务代码
        } catch (Exception e) {
            logger.error("Error executing scheduled task", e);
        }
    }
}

测试驱动开发

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
import org.springframework.test.context.ActiveProfiles;

@SpringBootTest
@ActiveProfiles("test")
class ScheduledTasksTest {

    @Autowired
    private ScheduledAnnotationBeanPostProcessor bpp;

    @Test
    void testScheduledTasks() {
        // 测试定时任务的行为
    }
}

监控与报警

设置监控指标来跟踪定时任务的执行情况,并配置报警机制,在任务未能按照预期执行时及时通知相关人员。

import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

// ...

public class ScheduledTasksConfigurer extends AsyncConfigurerSupport {

    @Override
    public ThreadPoolTaskExecutor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(200);
        executor.initialize();
        return executor;
    }
}

容错机制

设计容错机制来处理任务执行过程中的失败情况,例如重试策略、回滚机制等。

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;

@Retryable(value = Exception.class,
           maxAttempts = 3,
           backoff = @Backoff(delay = 1000, multiplier = 2))
public void retryableTask() {
    // 尝试执行任务,如果失败则根据策略重试
}

事务一致性

如果定时任务涉及数据库操作,确保这些操作在一个事务内完成,以维护数据的一致性。

import org.springframework.transaction.annotation.Transactional;

@Transactional
public void transactionalTask() {
    // 数据库操作应在事务中完成
}

使用注意事项

  • 确保应用运行时间:计划任务依赖于 Spring 应用的运行状态,因此需要保证应用在任务执行期间保持运行。

  • 异常处理:定时任务的异常处理尤为重要,因为它们往往在没有人工干预的情况下运行。建议对任务方法进行适当的异常捕获和日志记录。

  • 测试:在生产环境中部署之前,务必对定时任务进行全面的测试,特别是对于那些影响业务逻辑的关键任务。

  • 监控与告警:为了提高系统的健壮性,应该对定时任务的状态进行监控,并在任务失败时发出告警通知。

  • 时区问题:当涉及到不同地理位置的服务时,正确设置时区是非常重要的,以避免因时间差异导致的任务执行错误。

优点:

  • 灵活性:@Scheduled 支持多种调度模式,包括基于Cron的时间表以及基于固定时间间隔的方式。

  • 集成度高:作为 Spring 框架的一部分,@Scheduled 可以无缝集成到现有的 Spring Boot 应用中。

  • 易于使用:只需简单地添加注解即可定义计划任务,无需复杂的配置。

缺点:

  • 单体应用限制:由于任务是直接在 Spring 应用中运行的,因此如果应用停止或重启,定时任务也会受到影响。

  • 资源消耗:频繁的任务执行可能会占用较多的 CPU 和内存资源。

  • 并发控制:对于需要控制并发执行的任务,需要额外编写代码来保证。

Comment