Spring Boot 使用 @Scheduled 实现定时任务

最近有一个新项目需要写一个定时任务,因为没有涉及到复杂的功能需求,所以考虑仅使用 Spring 自带的 @Scheduled 注解实现

@Scheduled

基于注解实现定时任务的方式很简单,只需要在启动类前加上 @EnableScheduling 注解

1
2
3
4
5
6
@EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

然后在需要执行定时任务的方法前加上 @Scheduled() 注解,然后在括号内添加规则即可

1
2
3
4
5
6
7
8
9
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?

@Scheduled(cron = "0 0 2 * * ?")
public void func(){}

需要注意的是,通过 @Scheduled 实现的定时任务都是在一个线程中执行的,如果出现下一个定时任务开始时前一个还没有执行完的情况,下一个定时任务会阻塞直到前面的定时任务执行完。
此外, @Scheduled 不支持分布式的情况,如果部署到集群上,多台机器都会执行定时任务。所以如果在分布式的情况下,需要通过分布式锁来保证只有一台机器执行定时任务。

@Retryable

考虑到定时任务可能有执行失败的情况,执行失败后需要有重试机制。重试的最简单执行方式就是代码层面使用循环,为了保证业务逻辑的整洁,考虑在框架层面实现重试功能,所以选择使用 @Retryable 注解。
首先需要添加相关依赖

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>

然后在项目启动类前添加 @EnableRetry 注解

1
2
3
4
5
6
7
@EnableScheduling
@EnableRetry
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

然后在方法前加上 @Retryable 注解声明重试条件,当方法抛出对应异常后会触发重试

1
2
3
4
5
6
7
8
9
@Retryable参数的意思说明
value:抛出指定异常才会重试
include:和value一样,默认为空,当exclude也为空时,默认所以异常
exclude:指定不处理的异常
maxAttempts:最大重试次数,默认3
backoff:重试等待策略,默认使用@Backoff@Backoff的value默认为1000L,我们设置为2000L;multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒。

@Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
public void func(){}

如果达到最大重试次数依然抛出异常,可以通过 @Recover 注解来配置回调方法,可以基于此实现报警等功能

1
2
@Recover
public void alert(){}

注意:重试方法和回调方法需要写在同一个 Java 文件中

TODO

基于分布式锁让 @Schedule 兼容分布式
基于线程池让定时任务并发执行

参考

011 @Retryable的使用 - 曹军 - 博客园