07 xxl-job任务的3种阻塞处理策略

3,378 阅读2分钟

问题

当一个job正在运行的时候,如果发生以下情况该如何处理:

  1. 调度中心又主动触发该任务
  2. 该job耗时很长,到了其下次执行时间时,该job都还没执行完成,调度中心又发送了运行命令

一句话总结就是如果该任务发生阻塞了,该咋办

这个时候就该我们的阻塞处理策略上场了

任务的3种阻塞处理策略:单机串行、丢弃后续调度、覆盖之前调度

image.png

对应的java枚举类如下:

public enum ExecutorBlockStrategyEnum {  
    SERIAL_EXECUTION("Serial execution"),  
    DISCARD_LATER("Discard Later"),  
    COVER_EARLY("Cover Early");
}

阻塞处理策略只在以下代码中被使用到

// executor block strategy  
if (jobThread != null) {  
    // 任务的阻塞处理策略  
    ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(triggerParam.getExecutorBlockStrategy(), null);  
    if (ExecutorBlockStrategyEnum.DISCARD_LATER == blockStrategy) {  
        // discard when running 丢弃后续调度  
        if (jobThread.isRunningOrHasQueue()) {  
            return new ReturnT<>(ReturnT.FAIL_CODE, "block strategy effect:" + ExecutorBlockStrategyEnum.DISCARD_LATER.getTitle());  
        }  
    } else if (ExecutorBlockStrategyEnum.COVER_EARLY == blockStrategy) {  
        // kill running jobThread 覆盖之前调度  
        if (jobThread.isRunningOrHasQueue()) {  
            removeOldReason = "block strategy effect:" + ExecutorBlockStrategyEnum.COVER_EARLY.getTitle();  

            jobThread = null;  
        }  
    } else {  
        // just queue trigger 串行调度  
    }  
}

详细分析三种策略的不同及底层处理

单机串行:不理会之前的任务,直接将新的任务加入到任务队列(先入先出,实现串行)中,这个队列是 LinkedBlockingQueue triggerQueue,然后由JobThread线程不断轮询该队列,取出我们的job去执行我们的业务代码;

image.png

丢弃后续调度:如果该任务对应的JobThread正在处理任务或有任务要处理,就直接不管新的任务了,也不理会之前的任务,流程走完;否则同单机串行

if (jobThread.isRunningOrHasQueue()) { // 该任务对应的JobThread正在处理任务或有任务要处理  
    // 直接流程走完,也不理会之前的任务  
    return new ReturnT<>(ReturnT.FAIL_CODE, "block strategy effect:" + ExecutorBlockStrategyEnum.DISCARD_LATER.getTitle());  
}

覆盖之前调度:如果该任务对应的JobThread正在处理任务或有任务要处理,就将之前的任务丢掉并中断处理(下面江如何中断),创建一个新的任务;否则同单机串行

if (jobThread.isRunningOrHasQueue()) {  // 该任务对应的JobThread正在处理任务或有任务要处理
    removeOldReason = "block strategy effect:" + ExecutorBlockStrategyEnum.COVER_EARLY.getTitle();  
    // 丢掉之前的任务,等会重新创建一个  
    jobThread = null;  
}

在上面的代码中,如果jobThread有任务要处理,我们就将jobThread设置为null,然后重新创建一个新的任务。那么中断旧的任务?代码见下:

// replace thread (new or exists invalid)  
if (jobThread == null) {  
    jobThread = XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason);  
}

public static JobThread registJobThread(int jobId, IJobHandler handler, String removeOldReason) {  
    JobThread newJobThread = new JobThread(jobId, handler);  
    newJobThread.start();  

    JobThread oldJobThread = jobThreadRepository.put(jobId, newJobThread); // putIfAbsent | oh my god, map's put method return the old value!!!  
    if (oldJobThread != null) {  
        oldJobThread.toStop(removeOldReason);  
        oldJobThread.interrupt();  
    } 
    return newJobThread;  
}

中断旧任务的核心代码就两行:

oldJobThread.toStop(removeOldReason);
// 中断线程后,JobThread抛出中断异常,我们捕获该异常后再自己处理业务 oldJobThread.interrupt();

中断是通过Thread#interrupt方法实现的,所以正在处理的任务还是有可能继续运行,并不是说一中断正在运行的任务就终止了