Spring 定时任务,我遇到过的那几个坑

举报
yd_241780069 发表于 2026/10/31 12:23:42 2026/10/31
【摘要】 起因项目里有个对账任务,每天凌晨跑一次,把前一天的交易记录和第三方流水比一遍。用 @Scheduled 写上去,测试环境跑得好好的,上线之后出了问题:对账结果重复了两遍。查下来才发现,生产环境部署了两个实例,两个实例各自跑了一遍定时任务。@Scheduled 只在单机内保证不重复,跨实例它管不了。 单机下的坑也不少默认单线程。 加了好几个 @Scheduled 方法之后发现它们是串行执行的...

起因

项目里有个对账任务,每天凌晨跑一次,把前一天的交易记录和第三方流水比一遍。用 @Scheduled 写上去,测试环境跑得好好的,上线之后出了问题:对账结果重复了两遍。

查下来才发现,生产环境部署了两个实例,两个实例各自跑了一遍定时任务。@Scheduled 只在单机内保证不重复,跨实例它管不了。

单机下的坑也不少

默认单线程。 加了好几个 @Scheduled 方法之后发现它们是串行执行的,一个任务跑得慢,后面的全在等。原因是 Spring 默认给调度器配了一个单线程的 ThreadPoolTaskScheduler。要并行得自己配:

@Bean
public TaskScheduler taskScheduler() {
    ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
    scheduler.setPoolSize(5);
    scheduler.setThreadNamePrefix("sched-");
    return scheduler;
}

线程池大小按任务数量和耗时来定,别一拍脑袋写个 50,线程多了调度开销也上来了。

cron 表达式写错,不报错。 有一次我把 0 0 2 * * ? 写成了 0 0 2 * * *,不报错,但执行时间和预期不一样。Spring 的 cron 用的是 CronExpression,格式不对在启动时能抛异常,但“格式对、语义错”的情况不会提示,只能对着文档一个字段一个字段看。秒、分、时、日、月、周,六个字段,记不清就查,别凭感觉。

任务抛出异常,后续不执行。 @Scheduled 方法里抛了未捕获的异常,这个任务的下一次执行就停了。不是整个调度器停,是这一个任务停。日志里有个 Unexpected error occurred in scheduled task,不注意看根本发现不了。我现在所有定时任务方法体最外层都包一层 try-catch,记日志,不让异常往外冒。

多实例怎么办

回到开头那个问题。方案有这么几个:

一是加分布式锁。 用 Redis 的 setIfAbsent,任务开始前抢锁,抢到才执行,执行完释放。简单直接,但要处理锁超时和任务执行时间的关系,任务跑得比锁的 TTL 长,锁过期了另一个实例就能进来。

二是用数据库做标记。 任务开始前往一张表里插一条记录,唯一索引冲突就不执行。适合任务本身就是数据库操作的场景。

三是把定时任务抽出去。 用 XXL-JOB 或者 ElasticJob 这种调度中心,任务只在一台机器上触发。项目里实例多了之后我倾向这个方案,省得每个任务都写一遍锁的逻辑。

我们最后用的是 Redis 锁,因为只对账这一个任务需要互斥,不值得为此引入一整套调度框架。

还有几个零碎的

@Scheduled 的方法不能有参数,也不能有返回值。 写错了编译不报,启动的时候才抛 IllegalStateException。

cron 的时区。 容器里默认时区可能是 UTC,0 0 2 * * ? 指的是 UTC 凌晨两点,不是北京时间。要么在容器里配 TZ=Asia/Shanghai,要么在 cron 里显式指定时区,Spring 5.3 之后支持 zone 属性。

长时间运行的任务和优雅停机。 停机的时候如果任务跑到一半,默认会直接中断。Spring Boot 2.3 之后可以配 spring.task.scheduling.shutdown.await-termination=true 和 await-termination-period,等任务跑完再停,避免数据写一半。

小结

单机场景下,注意线程池、异常捕获、cron 写法这几件事就够了。多实例场景,@Scheduled 本身不解决互斥问题,得自己加锁或者换调度框架。我是从单机一路踩到多实例的,每一步都以为“这次没问题了”,然后被打脸。

先记这些。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。