一次XxlJob调度任务重复执行的问题排查

东老师的问题

东东老师:浪浪老师,我有问题了
我赶紧远离东老师:你有问题你去找路边的老中医广告的联系方式,什么延时、助勃、药到病除、金枪不倒,
那才是专业的,我不是你们那圈子的人
东东老师一挥手:我不是说这个,我的这边开发出了点问题,哦,还有你这个老中医的广告贴哪儿了,乱贴小广告不好,我去给它揭了
我:

害,吓我一跳,我以为东老师你终于要对我下手了呢
你QA开发能有啥问题

东老师:

是这样的,这次需求嘛 我开发了个定时任务用来清洗数据,使用的xxl-job,版本是XXXX,执行倒是没问题,问题是重复执行了

我立马摆摆手:

东老师你是知道我的,我从来不看框架源码,向来一把梭

东老师很爽快:

今天星期四,我kfc快到了

我:

东老师 你可是我异父异母的亲兄弟 你的事就是我的事!

看了下dev环境 xxl-job admin 控制台任务的配置,看了下任务的配置没啥毛病 每5分钟执行一次
我:东老师,你是怎么发现重复执行的
东老师:我在admin 控制台启动任务后,本地任务jobHandler执行了两次但是控制台的任务执行日志记录只有一条
我:东老师,太年轻了呀,软件开发讲究的就是一个玄学,万事不决,重启解决,一次不行,那就再来一次
东老师:我本地重启了好多次了
我:那你没考虑过重启xxl-job admin控制台的任务吗
东老师一脸无奈:我都把任务删除了重新配置handler,还是一样的结果,还是会重复两次
但是呢我发现了点奇怪的东西,当我把任务cron表达式执行频率改为1min一次之后就一切正常了,2min,3min ,5min都不行
我默默的又远离了东老师:我真的是不是0,你搞了这么多就为了暗示你是个1吗,什么只有1min才会重复执行,可能吗?你觉得我像那些会被你随随便 便欺骗的富老头吗。
东老师赶紧辩解:浪啊 一你不是我喜欢的类型 二你看你像富老头吗 你不相信我,还不相信等会儿的kfc吗


好吧,那我只好带着东老师的问题来排查下原因

1. 为什么会重复执行
2. 为什么时间间隔改为1min就不会重复执行**

开始排查

先看下任务配置

打开我的idea 切换分支 拉取分支 找到对应的jobhandler 如下图,发现配置以及代码看起来很正常,
注意 ,我说的是看起来正常,毕竟程序员写的代码,没有谁能预料到结果
在这里插入图片描述
对应的xxl-job admin 控制台任务配置如下
在这里插入图片描述
我之前没研究过xxl-job的源码 所以第一时间想的是github 看看issue 有没有类似的问题 搜了一圈下来 没有找到
只好启动项目 debug模式,把dev环境的执行器地址配置我本地IP ,开启任务执行

由于对源码不熟悉 先把断点打在任务的入口处,根据堆栈信息看看从源码的哪个地方跳转过来的 并打上对应的断点.

任务第一次执行

排查执行类 ==》JobThread

第一次启动之后的确实进行了分别两次的执行,仔细观察下执行的堆栈信息,
可以看到执行我们自定义的任务就是这个**JobThread**类
在这里插入图片描述
在这里插入图片描述


JobThread的核心逻辑

第一次执行
第二次执行
仔细对比上面两个图片,至少有两个问题
1.第一次执行进来execute()接受的param居然是个null
2.两次执行任务的堆栈信息不同

在这里插入图片描述
关键任务执行类就是JobThread,我们来研究下它的主要作用

1.循环消费 一个阻塞队列 不断的去消费队列中TriggerParam 这个参数
private LinkedBlockingQueue<TriggerParam> triggerQueue;
2.看下TriggerParam,这正是我们在admin控制台配置的参数生成的实体

在这里插入图片描述

3.核心执行逻辑是下面两个执行点
134行处,执行的条件是配置了任务超时时间,新启动了一个异步线程来执行任务,我们配置的任务不会走到这个地方
152行处,是我们当前任务执行的逻辑

在这里插入图片描述
但是啊 请注意,第一次执行的参数为空的场景并没有从这块儿执行,而是如下 98行代码处执行的
在这里插入图片描述
东老师:所以这个方法为什么会被执行
执行逻辑是在这个执行线程JobThread刚刚启动,还没有进入while循环,点击这个init()方法,看了下实现类
在这里插入图片描述
在这里插入图片描述

第一个问题的结论

东老师写了个父类的同名方法init()用于本地测试,这个init()方法会在任务执行线程JobThread启动的时候执行一次,执行传参是"",所以第一次任务执行的时候拿不动执行参数☹☹☹

东老师,你是真该死啊,咱测试代码就不能删除掉吗,还有这命名太随意了吧
东老师无视我:那为什么1min不重复执行
我很生气:我说我杀人不眨眼,你问我眼睛干不干,东东,你玩我?
东老师打开刚收到的kfc外卖,没错,他拿起了一块上校鸡块,塞进了我的嘴巴
确实很好吃。
呐,东老师,那就再来看下我们开头提出的两个问题

  1. 为什么会重复执行
  2. 为什么时间间隔改为1min就不会重复执行
第一个问题已经解决,那么为什么频率1min就不重复执行呢。

我们从前面第一次执行和第二次执行的堆栈信息可以看出,
第一次执行是JobThread刚刚启动的时候,是异常逻辑
第二次执行是while循环不断消费队列中的任务,是正常执行逻辑
那么 可以猜测下1min不重复执行说明 没有第一次的执行,也就是线程没有刚刚启动,还一直处于while循环中,引出来以下的问题
1.JobThread线程什么时候启动
2.JobThread线程什么时候停止也就是while循环什么时候终止

通过第一次执行的堆栈信息,可以定位到JobThread的启动时机,就是下图中的ExecutorBizImpl 的run方法中
在这里插入图片描述
run()方法中,启动JobThread的时候会调用XxlJobExecutor.registJobThread()放进ConcurrentHashMap
而看到removeJobThread这个方法时,可以猜想下,
难道是在JobThread线程内while()循环1min之后调用removeJobThread来移除线程,从而触发了1min不重复执行的现象吗
查找调用点,果然在JobThread中有这样一个调用点
但这里并不是1min的限制,而是使用**idleTimes** 这个int变量来控制空闲执行次数,超过空闲执行30次(本地执行大概就是1min之后),线程就会销毁
在销毁之前的时间段内,任务无论启动频率是多少,都会复用这个JobThread,并不是新启动一个JobThread,所以不会重复执行。
在这里插入图片描述

在这里插入图片描述

总结

1.收到调度请求 根据当前jobId是否有已有jobThread线程存在
2.不存在则新建jobThread ,并本地ConcurrentHashMap存储
3.jobThread启动,会先执行init方法,也就是东老师用来本地测试的那个方法,然后进入while循环
4.把调度请求触发器triggerParam 放入jobThread中的队列中,while循环获取消费队列 =====正常任务的执行
5.消费队列空了之后,while空循环30次,就会停止jobThread,本地移除thread
5.1循环30次的时候,如果有任务调度进来就会继续消费 所以1min的任务一直都是同一个线程执行,不会创建新的jobthread 也不会执行init方法

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值