1. 从“堵车”到“高速路”:为什么你的定时任务跑不快?
不知道你有没有遇到过这种情况:你精心设计了好几个定时任务,比如一个每分钟去数据库捞一次数据做统计,另一个每小时清理一次临时文件,还有一个每天凌晨给用户发推送。刚开始跑得挺好,但业务量一上来,你就发现不对劲了。要么是某个任务执行时间太长,把后面的任务全堵住了;要么是到了某个整点,几个任务挤在一起,系统响应慢得像蜗牛,监控面板上一片飘红。
这其实就是典型的单线程定时任务调度瓶颈。Spring Boot里那个用起来超级方便的 @Scheduled 注解,默认就是个“老实人”,它只用一个线程来执行所有标注了它的任务。你可以把它想象成一个只有一个收银台的超市,不管来了多少顾客(定时任务),都得老老实实排队。如果一个顾客买了整整一购物车的东西(长耗时任务),后面的人就只能干等着。更糟的是,如果好几个顾客约好了同一时间到(多个任务cron表达式重合),他们也得在门口挤成一团,一个一个进。
这种设计在轻量级应用里没问题,但一旦任务多了、重了,它就成了系统性能的“阿喀琉斯之踵”。我早年就踩过这个坑,一个生成日报的任务跑了十分钟,直接导致后面所有每分钟执行的监控任务全部延迟,报警短信收到手软。所以,今天咱们就来彻底解决这个问题,把单行道的“村口小路”,升级成多车道的“高速公路”,核心武器就是 @Scheduled、@Async 和自定义线程池的三剑客组合。目标很简单:让不同的定时任务能真正并发执行,互不阻塞,把系统的吞吐量提上去。
2. 核心武器库:@Scheduled、@Async与线程池拆解
工欲善其事,必先利其器。在动手改造之前,咱们得先把手里这几样工具摸透。
2.1 @Scheduled:你的定时任务触发器
@Scheduled 是Spring框架提供的定时任务核心注解,用起来简单到令人发指。你只需要在方法上加上这个注解,再配个时间表达式,Spring就会在后台自动帮你调度。
@Component
public class BasicScheduledTask {
// 最常用的cron表达式,每分钟执行一次
@Scheduled(cron = "0 * * * * ?")
public void taskPerMinute() {
System.out.println("每分钟任务执行,时间:" + new Date());
}
// fixedRate:固定速率,上一次开始后间隔指定时间就执行下一次
@Scheduled(fixedRate = 5000) // 单位:毫秒
public void taskWithFixedRate() {
// 假设这里执行需要3秒
System.out.println("FixedRate任务开始,时间:" + new Date());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// fixedDelay:固定延迟,上一次执行结束后间隔指定时间再执行下一次
@Scheduled(fixedDelay = 5000)
public void taskWithFixedDelay() {
System.out.println("FixedDelay任务开始,时间:" + new Date());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这里要特别理解 fixedRate 和 fixedDelay 的区别,这直接关系到任务会不会“堵车”。fixedRate 是“闹钟模式”,不管上一次任务有没有执行完,时间到了它就尝试启动下一次。如果任务执行时间超过了间隔,那就会发生任务重叠,在默认单线程下,后一个任务会排队等待


308

被折叠的 条评论
为什么被折叠?



