目录
- 东老师的问题
- 1. 为什么会重复执行
- 2. 为什么时间间隔改为1min就不会重复执行**
- 开始排查
- 先看下任务配置
- 任务第一次执行
- 排查执行类 ==》`JobThread`
- JobThread的核心逻辑
- 1.循环消费 一个阻塞队列 不断的去消费队列中TriggerParam 这个参数
- 2.看下TriggerParam,这正是我们在admin控制台配置的参数生成的实体
- 3.核心执行逻辑是下面两个执行点
- 第一个问题的结论
- 第一个问题已经解决,那么为什么频率1min就不重复执行呢。
- 总结
东老师的问题
东东老师:浪浪老师,我有问题了
我赶紧远离东老师:你有问题你去找路边的老中医广告的联系方式,什么延时、助勃、药到病除、金枪不倒,
那才是专业的,我不是你们那圈子的人
东东老师一挥手:我不是说这个,我的这边开发出了点问题,哦,还有你这个老中医的广告贴哪儿了,乱贴小广告不好,我去给它揭了
我:
害,吓我一跳,我以为东老师你终于要对我下手了呢
你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外卖,没错,他拿起了一块上校鸡块,塞进了我的嘴巴
确实很好吃。
呐,东老师,那就再来看下我们开头提出的两个问题
- 为什么会重复执行
- 为什么时间间隔改为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方法