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

那年那 Bug

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

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

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

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

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

问题排查

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

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

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

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

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

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

深入排查

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

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

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

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

通过这段日志,使用 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 执行定时任务。

交给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 定时任务存在 Bug??的更多相关文章

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

    专注于Java领域优质,技术欢迎关注 作者: 鸭血粉丝 来自:Java极客技术 Hello~各位读者新年好,我是鸭血粉丝(大家可以称呼我为「阿粉」).这里阿粉给大家拜个年,祝大家蒸蒸日上烫烫烫,年年有 ...

  2. Spring定时任务有时候会莫名奇妙的终止?

    最近在是使用Spring配置定时定时任务(基于xml配置使用spring自带的定时任务),一开始使用没什么问题当使用久了就会出现有些定时任务自动停止了.(关于如何使用以及如何它的原理是啥,这里不进行阐 ...

  3. spring定时任务详解(@Scheduled注解)( 转 李秀才的博客 )

    在springMVC里使用spring的定时任务非常的简单,如下: (一)在xml里加入task的命名空间 xmlns:task="http://www.springframework.or ...

  4. 摆脱Spring 定时任务的@Scheduled cron表达式的困扰

    一.背景 最近因为需要,需要适用Spring的task定时任务进行跑定时任务,以前也接触过,但是因为懒没有好好地理解@Scheduled的cron表达式,这次便对它做了一个全方位的了解和任务,记录下来 ...

  5. spring 定时任务配置

    1.(易)如何在spring中配置定时任务? spring的定时任务配置分为三个步骤: 1.定义任务 2.任务执行策略配置 3.启动任务 (程序中一般我们都是到过写的,直观些) 1.定义任务 < ...

  6. spring定时任务

    需求:在tomcat启动时开启一个定时任务,即项目启动完成后,自动执行某一特定任务. 想法:容器启动时执行方法,最容易想到的就是servlet中可以配置load-on-startup,设置一个正整数也 ...

  7. spring定时任务(转载)

    在springMVC里使用spring的定时任务非常的简单,如下: (一)在xml里加入task的命名空间 xmlns:task="http://www.springframework.or ...

  8. Spring定时任务的几种实现

    近日项目开发中需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息,借此机会整理了一下定时任务的几种实现方式,由于项目采用spring框架,所以我都将结合 spring框架来介绍. ...

  9. Spring 定时任务2

    转载自http://www.cnblogs.com/nick-huang/p/4864737.html > 版本说明 <dependencies> <dependency> ...

随机推荐

  1. UE4 中的 C++ 与 蓝图交互

    1.Unreal 引擎提供了两种创建新 Gameplay 元素的方法:C++ 和 蓝图视觉脚本. 通过 C++,程序员构建基础游戏系统:设计师可以基于此系统为场景 / 游戏创建自定义的游戏玩法. 这种 ...

  2. 《提升能力,涨薪可待》-Java并发之AQS全面详解

    欢迎关注公众号[Ccww笔记],原创技术文章第一时间推出 一.AQS是什么?有什么用? AQS全称AbstractQueuedSynchronizer,即抽象的队列同步器,是一种用来构建锁和同步器的框 ...

  3. MongoDB not authorized for query - code 13 错误解决办法

    跟着教程走完到了鉴权阶段,不加 --auth 登陆正常,但会出现warning :没有鉴权,修改不会生效,此时登陆正常. 但是加上了--auth 启动之后加上密码登陆则无法登陆. 添加用户和鉴权: 先 ...

  4. python知识点总结02(不定时更新)

    请用至少两种方式实现m与n值交换m=10,n=5 # 方式一 temp = 0 m = 10 n = 5 print(f'方式一交换前,m:{},n:{}') temp = m m = n n = t ...

  5. cocos2dx Vec2

    //SE是坐标重叠部分 // returns true if segment A-B intersects with segment C-D. S->E is the overlap part ...

  6. 【题解/模板】P1248 加工生产调度(贪心)

    [题解/模板]P1248 加工生产调度(贪心) 分析: \(A\)流水线的时间是确定的,所以现在就是要让\(b\)的时间尽量短 \(tB > tA\),除非所有东西都不需要\(b\).(t指结束 ...

  7. ECShop二次开发指南-文件结构(二)

      ecshop文件架构说明 注意:因各版权不一,大概参考/* ECShop 2.5.1 的结构图及各文件相应功能介绍 ECShop2.5.1_Beta upload 的目录 ┣ activity.p ...

  8. 使用百度NLP接口对搜狐新闻做分类

    一.简介 本文主要是要利用百度提供的NLP接口对搜狐的新闻做分类,百度对NLP接口有提供免费的额度可以拿来练习,主要是利用了NLP里面有个文章分类的功能,可以顺便测试看看百度NLP分类做的准不准.详细 ...

  9. C#调用Fortran生成的DLL的方法报内存不足

    最近在研究一个程序,公司给的,程序是VB写的,程序里面还有一个计算的模型,用Fortran语言写的. 在调试到这个模型里面的方法时报错,说是内存不足,于是就在网上查找方法,看了两篇博客之后问题解决了. ...

  10. mysql索引创建和使用细节(二)

    上篇粗略记录当mysql字段类型是string,传入int类型参数后失效当问题. 现在测试下mysql字段是int类型,传参string类型会发生什么. 题外话,最近膝盖手术后还在家养伤中,只怪自己以 ...