问题
当一个job正在运行的时候,如果发生以下情况该如何处理:
- 调度中心又主动触发该任务
- 该job耗时很长,到了其下次执行时间时,该job都还没执行完成,调度中心又发送了运行命令
一句话总结就是如果该任务发生阻塞了,该咋办
这个时候就该我们的阻塞处理策略上场了
任务的3种阻塞处理策略:单机串行、丢弃后续调度、覆盖之前调度
对应的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去执行我们的业务代码;
丢弃后续调度:如果该任务对应的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方法实现的,所以正在处理的任务还是有可能继续运行,并不是说一中断正在运行的任务就终止了