定时任务莫名停止,Spring 定时任务存在 Bug???

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Hello~各位读者新年好,我是鸭血粉丝(大家可以称呼我为「阿粉」)。这里阿粉给大家拜个年,祝大家蒸蒸日上烫烫烫,年年有余屯屯屯。

那年那 Bug

春节放假,阿粉坐上高铁回家,路上阿粉突然想到一次生产问题。那是阿粉参加工作第一年,那一年国庆假期,阿粉提前一天请假回家办个护照。那时候阿粉刚开始负责的系统,所以工作日请假,还是有点担心,就怕问题看阿粉不在,悄然上门。

哎,真实越怕什么,就来什么。

51.jpg

高铁开到一半的时候,同事反馈系统不能获取最新的流水信息(流水信息通过 Spring 定时任务定时拉取)。阿粉心里一惊,立刻拔出电脑,连上 VPN,准备登上生产机器,查看系统情况。可是,高铁上网络大家也懂,很不稳定,连了好久连不上 VPN,只好远程指挥同事看一下系统日志。通过同事反馈的日志,发现拉取流水定时任务没有执行,进一步查看,阿粉发现整个系统其他的定时任务也都停止了。。。

这真是一个奇怪的的问题,这好端端的定时任务怎么会突然停止?

暂时想不到解决办法,只好指挥同事先重启应用。重启之后,暂时解决问题,定时任务重新开始执行,也获取到最新的付款流水信息。

问题排查

到家之后,阿粉立刻登上生产机器,查看系统日志。这里阿粉发现重启之前某一定时任务运行到一半,并且在这之后其他定时任务就没有再被执行。

通过系统日志,定位到了有问题的代码。

52.jpg

这里采用重试补偿策略,防止查询流水信息因为网络等问题发生偶发的失败。这个策略面对偶发的失败没什么问题,但是如果查询银行流水服务一直失败,这段代码就会陷入死循环。恰巧那段时间网络出现一些问题,导致这里查询一直处于失败。

增加最大重试次数,修复该 Bug53.jpg

修复之后,立刻将最新版本代码部署到生产系统,暂时解决了这个问题。

知识点:面对一些失败,可以采用重试补偿策略,重新执行,最大可能保证执行成功,但是这里切记设置合适的的重大的次数

深入排查

虽然问题解决了,但是阿粉心里还是存在一个疑惑,为何一个定时任务发生了阻塞,就会影响执行其他定时任务。阿粉最初的理解是不同的定时任务应该互相隔离,互不影响才对,真难到是 Spring 定时任务的 Bug 吗?

想到这里,阿粉决定写一个 Demo,复现问题,然后深入源码排查。54.jpg

启动程序,日志输出如下:

55.jpg

从日志可以看到,fixDelayMethod 方法执行之后进入休眠,直到休眠结束,cronMethod 定时任务才有机会被执行。另外从上面可以看到,上述两个定时任务都由 pool-1-thread-1线程执行。从这点可以看出 Spring 定时任务将会交给线程池执行。

知识点: 线程池中线程默认命名策略为 pool-%poolNumber-thread-%num。

如果线程池只有一个工作线程,该线程一旦被长时间阻塞,堆积的其他任务就没有机会被执行。

那么是不是这个问题导致的 Sping 定时任务停止执行?我们继续往下排查。

图上日志绿色部分, ScheduledAnnotationBeanPostProcessor 输出一个重要信息:

No TaskScheduler/ScheduledExecutorService bean found for scheduled processing

查看 Spring 文档Spring 内部将会通过调用 TaskScheduler 执行定时任务,而另一个 ScheduledExecutorServiceJDK 提供执行定时任务的执行器。记住这两者

56.jpg

通过这段日志,使用 IDEA 的强大的关键字搜索功能,定位到 ScheduledAnnotationBeanPostProcessor#finishRegistration 方法。


57.jpg

这个方法比较长,大家重点关注图中标示的几处。

Spring 启动之后将会扫描所有 Bean 中带有 @Scheduled 注解的方法,然后封装成 Task 子类放置到 ScheduledTaskRegistrar

这段代码位于 ScheduledAnnotationBeanPostProcessor#processScheduled,感兴趣的可以翻阅查看

如果此时 ScheduledTaskRegistrar 不存在定时任务或者 ScheduledTaskRegistrar 中的 TaskScheduler不存在,finishRegistration将会多次调用 ScheduledAnnotationBeanPostProcessor#resolveSchedulerBean 方法用以查找 TaskScheduler/ScheduledExecutorService

58.jpg

接下去将会把获取到 Bean 通过 setScheduler 注入到 ScheduledTaskRegistrar 中。


59.jpg

如果获取的为 ScheduledExecutorService 类型,将会将其封装到 taskScheduler中。

最后还没找到,将会输出最刚开始见到的日志。然后 Spirng 将会在 ScheduledTaskRegistrar#afterPropertiesSet 创建一个单线程的定时任务执行器 ScheduledExecutorService,注入到 ConcurrentTaskScheduler中,然后通过 taskScheduler 执行定时任务。

60.jpg

image.jpeg

image-20200125144040781

交给TaskScheduler 的定时任务最后实际上还是通过 ScheduledExecutorService执行。62.jpg

这里可以得出一个结论

Spring 定时任务实际上通过 JDK 提供的 ScheduledExecutorService执行。默认情况下,Spring 将会生成一个单线程ScheduledExecutorService执行定时任务。所以一旦某一个定时任务长时间阻塞这个执行线程,其他定时任务都将被影响,没有机会被执行线程执行。

Spring 这种默认配置,在需要执行多个定时任务的情况,可能会是一个坑。我们可以通过改变配置,使 Spring 采用多线程执行定时任务。

自定义配置

Spring 可以通过多种方式改变默认配置。

xml 配置

通过 xml 配置 TaskScheduler 线程数。

<task:annotation-driven  scheduler="myScheduler"/>
<task:scheduler id="myScheduler" pool-size="10"/>

通过上面的配置,Spring 将会使用 TaskScheduler 子类 ThreadPoolTaskScheduler,内部线程数为 pool-size 数量,这个线程数将会直接设置 ScheduledExecutorService 线程数量。

注解配置

在上面问题排查中,我们知道 Spring 将会查找 TaskScheduler/ScheduledExecutorService,若存在将会使用。所以这里我们可以生成这些类的 Bean

63.jpg

以上方式二选一即可

SpringBoot 配置

上面两种配置适用于普通 Spring,比较繁琐。相比而言 SpringBoot 配置将会非常简单,只需要在启动配置文件加入如下配置即可。

spring.task.scheduling.pool.size=10
spring.task.scheduling.thread-name-prefix=task-test

技术总结

下面开始技术总结:

  1. Spring 定时任务执行原理实际使用的是 JDK 自带的 ScheduledExecutorService
  2. Spring 默认配置下,将会使用具有单线程的 ScheduledExecutorService
  3. 单线程执行定时任务,如果某一个定时任务执行时间较长,将会影响其他定时任务执行
  4. 如果存在多个定时任务,为了保证定时任务执行时间的准确性,可以修改默认配置,使其使用多线程执行定时任务
  5. 面对偶发的失败,我们可以采用重试补偿策略,不过这里切记设置合适的最大重试次数

随便聊聊

对于常用的开源框架,我们不仅要掌握怎么用,还要熟悉相关的配置,最后还应该去了解其内部的使用的原理。这样出了问题,我们也能很快定位问题,找到问题的实际原因。

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
2月前
|
监控 Java BI
《深入理解Spring》定时任务——自动化调度的时间管理者
Spring定时任务通过@Scheduled注解和Cron表达式实现灵活调度,支持固定频率、延迟执行及动态配置,结合线程池与异常处理可提升可靠性,适用于报表生成、健康检查等场景,助力企业级应用自动化。
|
6月前
|
Java Spring
使用 Spring Boot 多个定时任务阻塞问题的解决方案
我是小假 期待与你的下一次相遇 ~
357 5
|
6月前
|
存储 前端开发 Java
|
11月前
|
Java 调度 Spring
Spring之定时任务基本使用篇
本文介绍了在Spring Boot项目中使用定时任务的基本方法。主要通过`@Scheduled`注解实现,需添加`@EnableScheduling`开启定时任务功能。文中详细解析了Cron表达式的语法及常见实例,如每秒、每天特定时间执行等。此外,还探讨了多个定时任务的执行方式(并行或串行)及其潜在问题,并留待后续深入讨论。
387 64
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
定时任务在企业应用中至关重要,常用于异步数据处理、自动化运维等场景。在单体应用中,利用Java的`java.util.Timer`或Spring的`@Scheduled`即可轻松实现。然而,进入微服务架构后,任务可能因多节点并发执行而重复。Spring Cloud Alibaba为此发布了Scheduling模块,提供轻量级、高可用的分布式定时任务解决方案,支持防重复执行、分片运行等功能,并可通过`spring-cloud-starter-alibaba-schedulerx`快速集成。用户可选择基于阿里云SchedulerX托管服务或采用本地开源方案(如ShedLock)
364 1
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
Spring Cloud Alibaba 发布了 Scheduling 任务调度模块 [#3732]提供了一套开源、轻量级、高可用的定时任务解决方案,帮助您快速开发微服务体系下的分布式定时任务。
15974 114
|
Java BI 调度
Java Spring的定时任务的配置和使用
遵循上述步骤,你就可以在Spring应用中轻松地配置和使用定时任务,满足各种定时处理需求。
710 2
|
存储 Java API
简单两步,Spring Boot 写死的定时任务也能动态设置:技术干货分享
【10月更文挑战第4天】在Spring Boot开发中,定时任务通常通过@Scheduled注解来实现,这种方式简单直接,但存在一个显著的限制:任务的执行时间或频率在编译时就已经确定,无法在运行时动态调整。然而,在实际工作中,我们往往需要根据业务需求或外部条件的变化来动态调整定时任务的执行计划。本文将分享一个简单两步的解决方案,让你的Spring Boot应用中的定时任务也能动态设置,从而满足更灵活的业务需求。
938 4
|
Java 开发者 Spring
Spring Boot实战宝典:揭秘定时任务的幕后英雄,让业务处理如流水般顺畅,轻松驾驭时间管理艺术!
【8月更文挑战第29天】在现代应用开发中,定时任务如数据备份、报告生成等至关重要。Spring Boot作为流行的Java框架,凭借其强大的集成能力和简洁的配置方式,为开发者提供了高效的定时任务解决方案。本文详细介绍了如何在Spring Boot项目中启用定时任务支持、编写定时任务方法,并通过实战案例展示了其在业务场景中的应用,同时提供了注意事项以确保任务的正确执行。
145 0
|
Dubbo Java 调度
揭秘!Spring Cloud Alibaba的超级力量——如何轻松驾驭分布式定时任务调度?
【8月更文挑战第20天】在现代微服务架构中,Spring Cloud Alibaba通过集成分布式定时任务调度功能解决了一致性和可靠性挑战。它利用TimerX实现任务的分布式编排与调度,并通过`@SchedulerLock`确保任务不被重复执行。示例代码展示了如何配置定时任务及其分布式锁,以实现每5秒仅由一个节点执行任务,适合构建高可用的微服务系统。
343 0

热门文章

最新文章