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表达式在跑接口以后就变了

1652238242(1)

满心欢喜地等到相应时间点…

纳尼?我的业务压根没动静?难道是job没跑?

image

大哥,都过了一分钟了,你咋还在running状态呢?人家正常情况下单次的job执行完就默认变成end状态了。

那再看看日志吧。

image-1652238405163

嗯?什么都没有?这是什么情况?难道是xxl-job出bug了?行吧,那我试试看手动改一下job的cron。

image-1652238452929

给你改到55分,看看情况。

image-1652238520288

时间一到,准时执行!真是离离原上谱啊!!!

既然控制台没有问题,那问题肯定是出在了update接口那边,那我们就对比一下二者的源码咯。

3.问题排查

只要抓出xxl-job更新的接口和自己写的直接操作数据库更新接口的区别,那就可以知道发生了什么问题。

我倒是也很轻易就发现了猫腻。
image-1652238751118

image-1652238771110

那既然二者用的是同一个数据库表,那就去看看数据库里字段是什么。

image-20211231110642784

那基本就破案了,调用的时候我没有单独修改过这个triggerNextTime,那这个字段是必不可能改变的,所以只要把这个字段赋值就好了…等等!

我调用添加job的时候只是普通的insert了一条记录进数据库,也没赋值这个字段啊!但是添加job的时候是好的啊!这不科学!

那我们就研究一下xxl-job里哪些操作是操作了这个triggerNextTime字段。

找到了在JobScheduleHelper类里有对这个字段的操作,整个类原码比较长就不贴了,他的思路就是开一个线程去扫描所有定时任务,找到该执行的就执行。

在refreshNextValidTime中我找到了更新triggerNextTime的踪迹。

image-20211231111746491

image-20211231111753716

追一下什么时候才会去调这个方法

image-20211231112157843

基本上有一点眉目了,在查出来符合条件的jobinfo之后,会根据当前时间和triggerNextTime进行对比,那如果我的triggerNextTime没有更新,就算cron表达式更新了,他也不会按照表达式执行,而是根据之前refreshNextValidTime中设置的triggerNextTime的时间运行。

但还是解释不通为什么插入以后job会正常执行,继续看一看for循环中jobinfo的取值逻辑。

image-20211231112447351

image-20211231112500770

找到sql语句,发现有两个条件,一个是triggerStatus,也就是job是否启动,这里过滤的状态是启动中,另一个就是triggerNextTime,他是要求小于等于我们传入的maxNextTime,也就是调用时候给到的nowTime+PRE_READ_MS。

所以说他只会去扫描比当前时间戳要小的job进行判断,我的更新操作是把未来的时间提前,但是因为triggerNextTime没变化,还是未来的日期,所以我的这个jobinfo肯定是无缘被扫描到了。

同时也能解释通为什么add操作是没问题的。

image-20211231113011825

triggerNextTime字段默认值是0,而0是肯定小于当前时间戳的,所以无论如何他都会被扫描到。扫描到之后会走refreshNextValidTime方法把triggerNextTime更新,这样任务就能正常执行了。

4.解决方案

image-1652239716269

经过一系列的分析,发现最初提出的方案确实是站得住脚的,所以只要在传了触发时间之后更新triggerNextTime字段就可以了。