文章目录
前言
本文对执行器端 job 的运行进行介绍。
一、 任务执行回顾:
在之前的文章中,我们知道了在执行器端,启动后会开启一个netty 服务并监听我们在配置文件中 xxl.job.executor.port 设置的端口;当服务端触发任务执行的时候会发送http请求到到执行器端,执行器端会在 channelRead0 方法中最终进过下面的方法拉起任务的执行:
二、任务执行:
通过追踪代码可以发现 executorBiz.run(triggerParam) 会调用到 ExecutorBizImpl 的run 方法;
2.1 ExecutorBizImpl run:
2.1.1 任务线程&处理器(handler):
@Override
public ReturnT<String> run(TriggerParam triggerParam) {
// load old:jobHandler + jobThread
// 通过任务id 获取到执行该任务的专属线程,
// 如果是第一次执行则 获取到的 jobThread 为null
JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId());
// 通过获取到的jobThread 得到之前注册的执行该任务具体对应spring 中的某个bean 方法
// 如果是第一次执行则 获取到的 jobHandler 为null
IJobHandler jobHandler = jobThread!=null?jobThread.getHandler():null;
String removeOldReason = null;
// valid:jobHandler + jobThread
// 任务执行的方式是bean 执行还是脚本执行
GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType());
// 这里只对 bean 执行的方式进行内容注释
if (GlueTypeEnum.BEAN == glueTypeEnum) {
// new jobhandler
// 获取到本次服务端发送给客户端任务 对应的处理器 Handler
IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());
// valid old jobThread
/**
* 这里对 jobThread 和jobHandler 的情况进行列举
* 1 第一次任务重则 jobThread 为null
* 2 任务不是第一次执行则 根据任务id 获取到之前相同任务线程jobThread
* 此时判断服务端是否修改了 handler 执行器,如果修改了handler
* 则需要将任务重置(即 new 新的 jobThread 继续进行任务的执行)
* 之前任务的 jobThread 需要被抛弃掉
**/
if (jobThread!=null && jobHandler != newJobHandler) {
// change handler, need kill old thread
removeOldReason = "change jobhandler or glue type, and terminate the old job thread.";
jobThread = null;
jobHandler = null;
}
// valid handler
/**
* 1 任务是第一次执行任务 则 jobHandler就是 执行项目被 @XxlJob 标注的handler
* 2 如果不是第一次执行任务,并且本次任务修改了handler 执行器,
* 则jobHandler 会进入上面的if 判断并设置为null
**/
if (jobHandler == null) {
// 修改了handler 执行器 赋值新的 handler 执行器
jobHandler = newJobHandler;
if (jobHandler == null) {
// 执行器为空,则说明执行器项目在spring 项目中没有找到对应的handler
// 无法触发任务的执行(很可能是在admin
// 端新增任务是填入的 JobHandler 有问题;
// 和 @XxlJob 标注的handler中设置的name 不匹配)
return new ReturnT<String>(ReturnT.FAIL_CODE, "job handler [" + triggerParam.getExecutorHandler() + "] not found.");
}
}
} else if (GlueTypeEnum.GLUE_GROOVY == glueTypeEnum) {
// 脚本执行不在展开
// valid old jobThread
if (jobThread != null &&
!(jobThread.getHandler() instanceof GlueJobHandler
&& ((GlueJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
// change handler or gluesource updated, need kill old thread
removeOldReason = "change job source or glue type, and terminate the old job thread.";
jobThread = null;
jobHandler = null;
}
// valid handler
if (jobHandler == null) {
try {
IJobHandler originJobHandler = GlueFactory.getInstance().loadNewInstance(triggerParam.getGlueSource());
jobHandler = new GlueJobHandler(originJobHandler, triggerParam.getGlueUpdatetime());
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new ReturnT<String>(ReturnT.FAIL_CODE, e.getMessage());
}
}
} else if (glueTypeEnum!=null && glueTypeEnum.isScript()) {
// 脚本执行不在展开
// valid old jobThread
if (jobThread != null &&
!(jobThread.getHandler() instanceof ScriptJobHandler
&& ((ScriptJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
// change script or gluesource updated, need kill old thread
removeOldReason = "change job source or glue type, and terminate the old job thread.";
jobThread = null;
jobHandler = null;
}
// valid handler
if (jobHandler == null) {
jobHandler = new ScriptJobHandler(triggerParam.getJobId(), triggerParam.getGlueUpdatetime(), triggerParam.getGlueSource(), GlueTypeEnum.match(triggerParam.getGlueType()));
}
} else {
return new ReturnT<String>(ReturnT.FAIL_CODE, "glueType[" + triggerParam.getGlueType() + "] is not valid.");
}
// executor block strategy
// 如果 jobThread 不是null 则说明改任务不是第一次运行
// 这里要判断任务的执行策略,单机串行;丢弃后续调度,覆盖之前调度
if (jobThread != null) {
// 先获取任务的阻塞处理策略
ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(triggerParam.getExecutorBlockStrategy(), null);
if (ExecutorBlockStrategyEnum.DISCARD_LATER == blockStrategy) {
// 如果是丢弃后续调度 则说明要保留之前的任务,
// 并在该任务之前的任务还没有处理完毕的情况下
// 需要放弃后续同一个任务的执行
// discard when running
if (jobThread.isRunningOrHasQueue()) {
// 该任务之前的任务还没有处理完毕 则直接返回个服务端任务执行失败
return new ReturnT<String>(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();
// 将改任务所在线程置为null ,后续重新new jobThread
jobThread = null;
}
} else {
// 如果是单机串行则不做处理,因为后续逻辑回将改任务放入到队列中
// 依次从队列中取出执行任务
// just queue trigger
}
}
// replace thread (new or exists invalid)
/**
* 1 如果是第一次执行人则 jobThread 为null;
* 2 如果不是第一次执行该任务并且 任务是覆盖之前调度(改任务线程还有需要执行的任务)
* jobThread 则会被重置为null
**/
if (jobThread == null) {
jobThread = XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason);
}
// push data to queue
// 将任务方法到 对应任务线程的队列中
ReturnT<String> pushResult = jobThread.pushTriggerQueue(triggerParam);
return pushResult;
}
2.1.2 任务线程的创建 XxlJobExecutor.registJobThread:
// 任务线程的注册map key 为任务id ,value 为任务线程
private static ConcurrentMap<Integer, JobThread> jobThreadRepository = new ConcurrentHashMap<Integer, JobThread>();
public static JobThread registJobThread(int jobId, IJobHandler handler, String removeOldReason){
// 新建任务线程
JobThread newJobThread = new JobThread(jobId, handler);
// 启动线程
newJobThread.start();
logger.info(">>>>>>>>>>> xxl-job regist JobThread success, jobId:{}, handler:{}", new Object[]{jobId, handler});
// 将任务线程放入到map 中 key 为任务id ,value 为任务线程
// 得到之前的任务线程
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;
}
2.1.3 任务线程队列 jobThread.pushTriggerQueue:
/**
private LinkedBlockingQueue<TriggerParam> triggerQueue;
private Set<Long> triggerLogIdSet;
* new trigger to queue
*
* @param triggerParam
* @return
*/
public ReturnT<String> pushTriggerQueue(TriggerParam triggerParam) {
// avoid repeat 重复任务的判断
if (triggerLogIdSet.contains(triggerParam.getLogId())) {
logger.info(">>>>>>>>>>> repeate trigger job, logId:{}", triggerParam.getLogId());
return new ReturnT<String>(ReturnT.FAIL_CODE, "repeate trigger job, logId:" + triggerParam.getLogId());
}
// 对应改任务线程 jobThread 任务id set 集合
triggerLogIdSet.add(triggerParam.getLogId());
// 将任务添加到执行队列中
triggerQueue.add(triggerParam);
return ReturnT.SUCCESS;
}
2.2 triggerQueue 取出任务并执行:
2.2.1 任务的取出:
@Override
public void run() {
// init
try {
// @XxlJob("demoJobHandler") 中如果定义了 init() 方法则先进行init 的调用
handler.init();
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
// execute
while(!toStop){
// 线程执行
running = false;
// 空闲次数记录
idleTimes++;
// 任务对象 triggerParam
TriggerParam triggerParam = null;
try {
// to check toStop signal, we need cycle, so wo cannot use queue.take(), instand of poll(timeout)
triggerParam = triggerQueue.poll(3L, TimeUnit.SECONDS);
// 取的任务
if (triggerParam!=null) {
running = true;
// 重置空闲次数
idleTimes = 0;
// triggerLogIdSet set 集合移除任务
triggerLogIdSet.remove(triggerParam.getLogId());
// 组装 任务日志记录
// log filename, like "logPath/yyyy-MM-dd/9999.log"
String logFileName = XxlJobFileAppender.makeLogFileName(new Date(triggerParam.getLogDateTime()), triggerParam.getLogId());
// 日志上下文对象xxlJobContext 构建
XxlJobContext xxlJobContext = new XxlJobContext(
triggerParam.getJobId(),
triggerParam.getExecutorParams(),
logFileName,
triggerParam.getBroadcastIndex(),
triggerParam.getBroadcastTotal());
// init job context
// 存入到 threadlocal 中 ,方便后续任务执行过程中,进行日志信息记录
XxlJobContext.setXxlJobContext(xxlJobContext);
// execute
XxlJobHelper.log("<br>----------- xxl-job job execute start -----------<br>----------- Param:" + xxlJobContext.getJobParam());
// 任务执行的超时时间
if (triggerParam.getExecutorTimeout() > 0) {
// 如果设置了超时时间
// limit timeout
Thread futureThread = null;
try {
// 通过 FutureTask 获取任务执行结果
FutureTask<Boolean> futureTask = new FutureTask<Boolean>(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
// init job context
XxlJobContext.setXxlJobContext(xxlJobContext);
// 通过MethodJobHandler 反射调用方法执行任务
handler.execute();
return true;
}
});
futureThread = new Thread(futureTask);
futureThread.start();
// 按规定的超时时间 获取执行结果
Boolean tempResult = futureTask.get(triggerParam.getExecutorTimeout(), TimeUnit.SECONDS);
} catch (TimeoutException e) {
// 超时 日志记录
XxlJobHelper.log("<br>----------- xxl-job job execute timeout");
XxlJobHelper.log(e);
// handle result
XxlJobHelper.handleTimeout("job execute timeout ");
} finally {
futureThread.interrupt();
}
} else {
// just execute
// 没有设置任务的超时时间,直接进行任务的执行
handler.execute();
}
// valid execute handle data
// xxlJobContext 初始对象是设置的 handleCode 为200
if (XxlJobContext.getXxlJobContext().getHandleCode() <= 0) {
// 任务执行出现异常
XxlJobHelper.handleFail("job handle result lost.");
} else {
// 获取到 任务执行情况信息
String tempHandleMsg = XxlJobContext.getXxlJobContext().getHandleMsg();
tempHandleMsg = (tempHandleMsg!=null&&tempHandleMsg.length()>50000)
?tempHandleMsg.substring(0, 50000).concat("...")
:tempHandleMsg;
XxlJobContext.getXxlJobContext().setHandleMsg(tempHandleMsg);
}
XxlJobHelper.log("<br>----------- xxl-job job execute end(finish) -----------<br>----------- Result: handleCode="
+ XxlJobContext.getXxlJobContext().getHandleCode()
+ ", handleMsg = "
+ XxlJobContext.getXxlJobContext().getHandleMsg()
);
} else {
if (idleTimes > 30) {
// 如果空轮训此时超过 30 次 ,当前任务的队列没有可以执行的任务
if(triggerQueue.size() == 0) { // avoid concurrent trigger causes jobId-lost
// 从jobThreadRepository 移除执行改任务的线程,释放资源
XxlJobExecutor.removeJobThread(jobId, "excutor idel times over limit.");
}
}
}
} catch (Throwable e) {
if (toStop) {
XxlJobHelper.log("<br>----------- JobThread toStop, stopReason:" + stopReason);
}
// handle result
StringWriter stringWriter = new StringWriter();
e.printStackTrace(new PrintWriter(stringWriter));
String errorMsg = stringWriter.toString();
// 任务执行失败信息记录
XxlJobHelper.handleFail(errorMsg);
XxlJobHelper.log("<br>----------- JobThread Exception:" + errorMsg + "<br>----------- xxl-job job execute end(error) -----------");
} finally {
// 最后,将任务执行的结果信息放入到队列中,方便其他线程去轮询任务执行结果队列
// 并经任务记过回传给 服务端
if(triggerParam != null) {
// callback handler info
if (!toStop) {
// commonm
// 记录任务 正常执行后的结果(执行成功/执行失败)
TriggerCallbackThread.pushCallBack(new HandleCallbackParam(
triggerParam.getLogId(),
triggerParam.getLogDateTime(),
XxlJobContext.getXxlJobContext().getHandleCode(),
XxlJobContext.getXxlJobContext().getHandleMsg() )
);
} else {
// is killed
// 任务被停掉,则天机任务带失败 的队列
TriggerCallbackThread.pushCallBack(new HandleCallbackParam(
triggerParam.getLogId(),
triggerParam.getLogDateTime(),
XxlJobContext.HANDLE_CODE_FAIL,
stopReason + " [job running, killed]" )
);
}
}
}
}
// callback trigger request in queue
// 容器关闭,则将队列中的任务标记为执行失败
while(triggerQueue !=null && triggerQueue.size()>0){
TriggerParam triggerParam = triggerQueue.poll();
if (triggerParam!=null) {
// is killed
TriggerCallbackThread.pushCallBack(new HandleCallbackParam(
triggerParam.getLogId(),
triggerParam.getLogDateTime(),
XxlJobContext.HANDLE_CODE_FAIL,
stopReason + " [job not executed, in the job queue, killed.]")
);
}
}
// destroy
try {
// 回调 @XxlJob("demoJobHandler") 定义的destroy() 方法
handler.destroy();
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
logger.info(">>>>>>>>>>> xxl-job JobThread stoped, hashCode:{}", Thread.currentThread());
}
2.2.2 handler.execute() 任务的执行:
/**
* @author xuxueli 2019-12-11 21:12:18
*/
public class MethodJobHandler extends IJobHandler {
private final Object target;
private final Method method;
private Method initMethod;
private Method destroyMethod;
public MethodJobHandler(Object target, Method method, Method initMethod, Method destroyMethod) {
this.target = target;
this.method = method;
this.initMethod = initMethod;
this.destroyMethod = destroyMethod;
}
@Override
public void execute() throws Exception {
// 获取方法参数
Class<?>[] paramTypes = method.getParameterTypes();
// 反射调用
if (paramTypes.length > 0) {
method.invoke(target, new Object[paramTypes.length]); // method-param can not be primitive-types
} else {
method.invoke(target);
}
}
@Override
public void init() throws Exception {
if(initMethod != null) {
initMethod.invoke(target);
}
}
@Override
public void destroy() throws Exception {
if(destroyMethod != null) {
destroyMethod.invoke(target);
}
}
@Override
public String toString() {
return super.toString()+"["+ target.getClass() + "#" + method.getName() +"]";
}
}
2.2.3 任务执行结果队列添加:
/**
* job results callback queue
*/
private LinkedBlockingQueue<HandleCallbackParam> callBackQueue = new LinkedBlockingQueue<HandleCallbackParam>();
public static void pushCallBack(HandleCallbackParam callback){
// 队列的添加
getInstance().callBackQueue.add(callback);
logger.debug(">>>>>>>>>>> xxl-job, push callback request, logId:{}", callback.getLogId());
}
在 原理篇-- 定时任务xxl-job-执行器项目启动过程–TriggerCallbackThread 初始化(3) 我们对任务的回调过程进行了介绍。
总结
定时任务xxl-job–任务执行-run() 进行介绍。