原理篇-- 定时任务xxl-job--任务执行-run(5)


前言

本文对执行器端 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() 进行介绍。

  • 18
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值