各位,是不是每天上线、拉数据、生成报表、清缓存、发通知、重试失败任务这些琐事已经搞得你心力交瘁?别急,今天咱聊个实用到哭的“老朋友”:Spring Task。
讲真,这玩意儿看起来不起眼,写起来也就三行配置 + 一个注解,但用好了,能把你从一堆机械活中彻底解放出来。什么“凌晨定时清理缓存”、“定时同步第三方数据”、“每晚跑批任务”……全都交给它就行了,稳定、高效、还不要你盯着。
很多刚入坑的小伙伴可能以为定时任务得上Quartz、Elastic-Job、xxl-job那种大杀器才靠谱。但我跟你讲,八九不离十的业务需求,Spring Task 三板斧就能搞定。
先来个最简单的:
@Configuration
@EnableScheduling
public class ScheduleConfig {
}
@Component
public class MyTask {
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
public void clearCache() {
System.out.println("清理缓存完成");
}
}
没错,就这仨东西:一个注解@EnableScheduling,一个@Component Bean,加一个@Scheduled方法,你的定时任务就上线了。
想多定几个任务?加几个方法就行。 想控制执行频率?支持cron表达式、fixedRate、fixedDelay、initialDelay,怎么花式都可以。
比如每10秒执行一次:
@Scheduled(fixedRate = 10000)
public void autoReport() {
System.out.println("定时上报数据");
}
注意,fixedRate是从任务开始算,fixedDelay是从任务结束算,这两个要是搞混了,任务调度就会打架,别怪我没提醒你。
再说点我踩过的坑,不然你们觉得定时任务就这点活,那就小瞧它了。
第一坑:多线程并发执行,结果混乱或任务重叠
默认Spring Task是单线程执行的,一个任务卡住了,后面的任务都得排队。这个设计对我们来说有好有坏,坏是不能并发,好是不用担心同一个任务被多个线程抢着跑。但有时候你确实需要并发,比如十个城市同步天气数据,那你得手动开线程池:
@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
registrar.setScheduler(Executors.newScheduledThreadPool(5));
}
}
这时候,每个任务都能并发执行,但你得确保线程安全,别用个共享变量结果被改得乱七八糟,尤其是像静态变量、单例模式下的公共资源,那叫一个坑死人。
第二坑:任务执行太久,下一次调度时间到了还没跑完
想想你配置的是每10秒执行一次任务,如果某次任务因为网络抖动卡了15秒,那下一次调度就重叠了。你要不控制一下,可能直接两个线程一起跑同一段逻辑,结果数据库爆了、接口炸了、服务挂了。
解决方案也简单:搞个任务锁机制,比如用Redis的SETNX或者数据库加乐观锁,确保一个时间段内只允许一个任务执行。
第三坑:服务部署多个实例后任务重复执行
这个是最多人掉坑的场景之一。你本地测试好好的,一上线集群部署,结果你那“每天凌晨自动清缓存”任务,居然跑了三遍。为啥?因为你部署了三个实例,大家一起上岗……
解决方案其实也不复杂:要么通过分布式任务调度框架来统一控制(比如xxl-job、Quartz集群版),要么用Redis做分布式锁,拿到锁的那个实例执行任务,其他就乖乖等着。
我自己习惯是用Redisson做锁,写个注解 + AOP拦一下,很丝滑:
@Scheduled(cron = "0 0 * * * ?")
public void syncData() {
RLock lock = redissonClient.getLock("syncDataLock");
if (lock.tryLock()) {
try {
// 真正要执行的逻辑
} finally {
lock.unlock();
}
}
}
其实说到底,Spring Task真的很适合用在“轻量化定时任务”场景,尤其是那种日常重复任务、少量逻辑、无需复杂调度的。你搞个大框架纯属杀鸡用牛刀,不如这三行代码,清爽又好维护
当然了,要是你是那种“亿级别流量、高并发分布式任务调度”的场景,Spring Task就不够用了,建议你看看xxl-job、Quartz集群版、或者直接上K8s CronJob配合队列来异步执行,都比硬撸靠谱。
总结一下:
Spring Task适合中小项目、单体应用、定期执行的轻量任务;
用多线程配合线程池,可以避免任务卡死的问题;
注意任务锁和集群重复执行的坑,别把线上环境当本地;
轻量好维护,比那堆大而全的定时框架爽多了;
真要跑复杂调度的,记得上专业工具,不要强行用Spring Task上天。
说了这么多,也不想给Spring Task戴高帽子,就像它名字一样——Task,就是干活儿的,你别指望它变成“调度中心”,但你得知道,手里的小刀也是刀,关键时候照样能剁个猛的
你们公司都还在用什么跑定时任务?有没有那种“历史遗留”的怪异框架,一改任务得改XML配置还得重启?欢迎在评论区来个技术吐槽局,我已经准备好瓜子了