定时任务莫名停止,Spring 定时任务存在 Bug?? - 楼下小黑哥 - 博客园

标签: | 发表时间:2020-01-31 14:52 | 作者:
出处:https://www.cnblogs.com

Hello~各位读者新年好!这里楼下小黑哥给大家拜个年,祝大家蒸蒸日上烫烫烫,年年有余屯屯屯。

那年那 Bug

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

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

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

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

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

问题排查

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

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

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

增加最大重试次数,修复该 Bug

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

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

深入排查

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

想到这里,小黑哥决定写一个 Demo,复现问题,然后深入源码排查。

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

image-20200124160151622

从日志可以看到, 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提供执行定时任务的执行器。记住这两者

image-20200125140457573

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

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

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

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

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

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

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

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

image-20200125144040781

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

这里可以得出一个 结论

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

以上方式二选一即可

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. 面对偶发的失败,我们可以采用重试补偿策略,不过这里切记设置合适的最大重试次数

随便聊聊

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

帮助文档

Spring scheduling-task-scheduler

欢迎关注我的公众号:程序通事,获得日常干货推送。如果您对我的专题内容感兴趣,也可以关注我的博客: studyidea.cn


相关 [任务 spring 任务] 推荐:

Spring定时任务的几种实现

- - ITeye博客
Spring定时任务的几种实现. 近日项目开发中需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息,借此机会整理了一下定时任务的几种实现方式,由于项目采用spring框架,所以我都将结合. 从实现的技术上来分类,目前主要有三种技术(或者说有三种产品):. Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务.

quartz spring 实现动态定时任务

- - 企业架构 - ITeye博客
在实际项目应用中经常会用到定时任务,可以通过quartz和spring的简单配置即可完成,但如果要改变任务的执行时间、频率,废弃任务等就需要改变配置甚至代码需要重启服务器,这里介绍一下如何通过quartz与spring的组合实现动态的改变定时任务的状态的一个实现. 参考文章: http://www.meiriyouke.net/?p=82.

Spring+quartz 实现动态管理任务

- - 寒江孤影
在实际项目应用中经常会用到定时任务,可以通过quartz和spring的简单配置即可完成,但如果要改变任务的执行时间、频率,废弃任务等就需要改变配置甚至代码需要重启服务器,这里介绍一下如何通过quartz与spring的组合实现动态的改变定时任务的状态的一个实现. 本文章适合对quartz和spring有一定了解的读者.

Java Spring注解任务调度并实现AOP监控任务执行情况

- - 极客521 | 极客521
本文讲的是通过Spring注解的方式实现任务调度. 只要引入了spring-context包就能够在项目中使用注解方式的任务调度. 需要在Spring配置文件中加入task的schema. 然后在代码中就可以直接用了,要定时执行的方法必须是void的,并且没有任何参数的. cron表达式请自行问百度,下面只列出几个从网上找的例子.

Spring实现后台的任务调度TimerTask和Quartz

- - CSDN博客互联网推荐文章
最近整后台,涉及到两个后台调度的问题. 一是以时间间隔为条件的轮询调度;. 运用场景:每隔5分钟抓取数据;. 二是一某个时间点为条件的轮询调度;. 运用场景:后台日志货报表生成上传,每个周一生成上一周的,每个月初生成上一月. 其实按周来执行调度,用前面一个场景也可以实现,但是按月生成,因为每月时间不固定,必须动态判断和执行.

Spring 任务调度Quartz的cron表达式

- - ITeye博客
Spring支持基于Quartz的任务调度,那么其cron表达式类似于Linux的crontab,有7个字符构成,详情如下:. 表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五. 表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即等同于10,11,12.

Spring+Quartz实现动态添加定时任务

- - 编程语言 - ITeye博客
   0 0 0 * * ?. //如果全部定时任务都要动态生成,可以只配置这一个即可. * Description: 计时器工具类. private static Scheduler scheduler;// 调度器.   * Description: 启动一个自定义的job.

Spring Boot实现定时任务的动态增删启停

- - 程序猿DD
在Spring Boot项目中,可以通过 @EnableScheduling注解和 @Scheduled注解实现定时任务(在DD的Spring Boot教程中就有介绍: 使用@Scheduled实现定时任务. ),也可以通过 SchedulingConfigurer接口来实现定时任务. 但是这两种方式不能动态添加、删除、启动、停止任务.

定时任务莫名停止,Spring 定时任务存在 Bug?? - 楼下小黑哥 - 博客园

- -
这里楼下小黑哥给大家拜个年,祝大家蒸蒸日上烫烫烫,年年有余屯屯屯. 春节放假,小黑哥坐上高铁回家,突然想到一次生产问题. 那是小黑哥参加工作第一年,那一年国庆假期,小黑哥提前一天请假回家办个护照. 那时候刚开始负责一个生产系统,所以工作日请假,还是有点担心,就怕. 问题看小黑哥不在,悄然上门. 高铁开到一半的时候,同事反馈系统不能获取最新的流水信息(流水信息通过.

任务完成

- pp2moonbird - YesKafei Daily
这段令人激动又伤感的视频,集合了航天飞机的自始至终. 你可以一览航天飞机执行的所有任务,人类探索太空的渴望和梦想,但也看到了挑战者号和哥伦比亚号的悲剧画面. 如同视频中的配乐,每一次失败,并不能动摇航天飞机冲向太空的决心,航天飞机的再次发射升空令失去得到尊重. 现在,她完美的谢幕,科技永不止步,下一代STS(Space Transportation System)会更强大.