1.概述
现在有一个修改单次执行的xxl-job执行时间的需求,寻思着自己实现的xxl-job包里有更新job的接口,我想着就没必要删了任务重新建,改一个job执行时间应该就完事了。
2.事件概况
我根据api里的注释,加好了所有参数,在这里只有一个触发时间发生了变更。
public void updateJob(Date jobTime) {
XxlJobInfoUpdateIn xxlJobInfoUpdateIn = new XxlJobInfoUpdateIn();
......
//原本是00 00 00 30 01 ? 2022,改为了00 50 10 31 12 ? 2021
xxlJobInfoUpdateIn.setTriggerTime(DateTrans.getOneTimeCron(jobTime));
jobService.updateJob(xxlJobInfoUpdateIn);
}
可以看到cron表达式在跑接口以后就变了
满心欢喜地等到相应时间点…
纳尼?我的业务压根没动静?难道是job没跑?
大哥,都过了一分钟了,你咋还在running状态呢?人家正常情况下单次的job执行完就默认变成end状态了。
那再看看日志吧。
嗯?什么都没有?这是什么情况?难道是xxl-job出bug了?行吧,那我试试看手动改一下job的cron。
给你改到55分,看看情况。
时间一到,准时执行!真是离离原上谱啊!!!
既然控制台没有问题,那问题肯定是出在了update接口那边,那我们就对比一下二者的源码咯。
3.问题排查
只要抓出xxl-job更新的接口和自己写的直接操作数据库更新接口的区别,那就可以知道发生了什么问题。
我倒是也很轻易就发现了猫腻。
那既然二者用的是同一个数据库表,那就去看看数据库里字段是什么。
那基本就破案了,调用的时候我没有单独修改过这个triggerNextTime,那这个字段是必不可能改变的,所以只要把这个字段赋值就好了…等等!
我调用添加job的时候只是普通的insert了一条记录进数据库,也没赋值这个字段啊!但是添加job的时候是好的啊!这不科学!
那我们就研究一下xxl-job里哪些操作是操作了这个triggerNextTime字段。
找到了在JobScheduleHelper类里有对这个字段的操作,整个类原码比较长就不贴了,他的思路就是开一个线程去扫描所有定时任务,找到该执行的就执行。
在refreshNextValidTime中我找到了更新triggerNextTime的踪迹。
追一下什么时候才会去调这个方法
基本上有一点眉目了,在查出来符合条件的jobinfo之后,会根据当前时间和triggerNextTime进行对比,那如果我的triggerNextTime没有更新,就算cron表达式更新了,他也不会按照表达式执行,而是根据之前refreshNextValidTime中设置的triggerNextTime的时间运行。
但还是解释不通为什么插入以后job会正常执行,继续看一看for循环中jobinfo的取值逻辑。
找到sql语句,发现有两个条件,一个是triggerStatus,也就是job是否启动,这里过滤的状态是启动中,另一个就是triggerNextTime,他是要求小于等于我们传入的maxNextTime,也就是调用时候给到的nowTime+PRE_READ_MS。
所以说他只会去扫描比当前时间戳要小的job进行判断,我的更新操作是把未来的时间提前,但是因为triggerNextTime没变化,还是未来的日期,所以我的这个jobinfo肯定是无缘被扫描到了。
同时也能解释通为什么add操作是没问题的。
triggerNextTime字段默认值是0,而0是肯定小于当前时间戳的,所以无论如何他都会被扫描到。扫描到之后会走refreshNextValidTime方法把triggerNextTime更新,这样任务就能正常执行了。
4.解决方案
经过一系列的分析,发现最初提出的方案确实是站得住脚的,所以只要在传了触发时间之后更新triggerNextTime字段就可以了。