xxl-job是如何执行任务的?

背景

书接上文,我们了解了xxl-job,admin端是如何发送消息到客户端,今天我们就来分析一下,客户端如何找到对应的jobhandler并执行的

源码解析

之前我们分析到,xxl-job会在客户端,通过netty启动一个服务器处理对应的http请求,所以根据这个,我们进入到executorBiz.run方法中

arduino 复制代码
            // services mapping
            try {
                switch (uri) {
                    case "/beat":
                        return executorBiz.beat();
                    case "/idleBeat":
                        IdleBeatParam idleBeatParam = GsonTool.fromJson(requestData, IdleBeatParam.class);
                        return executorBiz.idleBeat(idleBeatParam);
                    case "/run":
                        TriggerParam triggerParam = GsonTool.fromJson(requestData, TriggerParam.class);
                        return executorBiz.run(triggerParam);
                    case "/kill":
                        KillParam killParam = GsonTool.fromJson(requestData, KillParam.class);
                        return executorBiz.kill(killParam);
                    case "/log":
                        LogParam logParam = GsonTool.fromJson(requestData, LogParam.class);
                        return executorBiz.log(logParam);
                    default:
                        return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping(" + uri + ") not found.");
                }
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                return new ReturnT<String>(ReturnT.FAIL_CODE, "request error:" + ThrowableUtil.toString(e));
            }

目前,我们只研究bean模式,所以其他模式先省略

ini 复制代码
// load old:jobHandler + jobThread
        // 若以前执行过,则根据jobid能获取到对应的任务线程
        JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId());
        IJobHandler jobHandler = jobThread!=null?jobThread.getHandler():null;
        String removeOldReason = null;

        // valid:jobHandler + jobThread  判断任务的执行模式
        GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType());
        if (GlueTypeEnum.BEAN == glueTypeEnum) {

            // new jobhandler
            // 根据任务名称,获取当前执行的handler
            IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());

            // valid old jobThread
            // 如果当前线程的jobhandler不等于正在执行的jobhandler则清空
            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;
            }

            // 如果当前jobhandler为空,则将需要执行的jobhandler赋值
            if (jobHandler == null) {
                jobHandler = newJobHandler;
                if (jobHandler == null) {
                    return new ReturnT<String>(ReturnT.FAIL_CODE, "job handler [" + triggerParam.getExecutorHandler() + "] not found.");
                }
            }

        } else if (GlueTypeEnum.GLUE_GROOVY == glueTypeEnum) {
         ....
        } else if (glueTypeEnum!=null && glueTypeEnum.isScript()) {
            .....
        } else {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "glueType[" + triggerParam.getGlueType() + "] is not valid.");
        }

        // 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<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();

                    jobThread = null;
                }
            } else {
                // just queue trigger
            }
        }

        // replace thread (new or exists invalid)
        // 线程为空,则将线程进行注册
        if (jobThread == null) {
            jobThread = XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason);
        }

        // push data to queue
        // 加入执行队列
        ReturnT<String> pushResult = jobThread.pushTriggerQueue(triggerParam);
        return pushResult;

这里主要是初始化job执行的线程,同时进行缓存,用来判断当前执行器是否有阻塞,如果阻塞的话,根据不同的策略选择覆盖还是终止,点开jobThread.pushTriggerQueue()方法

csharp 复制代码
 private LinkedBlockingQueue<TriggerParam> triggerQueue;    
    /**
     * 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());
  }

  triggerLogIdSet.add(triggerParam.getLogId());
  triggerQueue.add(triggerParam);
        return ReturnT.SUCCESS;
 }

这块主要是将线程存入到队列进行消费,从这里也能窥见点xxl-job对于线程的使用。 那这个队列是怎么哪消费的呢?其实也很简单。jobThread,既然都带个thread,说明它是一个thread对象,那执行的方法,肯定就是run方法了。整个run方法太长了,而且基本都是线程执行调用,具体的还是下次解析xxl-job线程使用的时候再讲

typescript 复制代码
 public void run() {

     // 初始化执行器
     try {
   handler.init();
  } catch (Throwable e) {
      logger.error(e.getMessage(), e);
  }

  // execute
  while(!toStop){
   running = false;
   idleTimes++;
            // 获取触发器参数
            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);
                // 不等于null,则执行
    if (triggerParam!=null) {
     running = true;
     idleTimes = 0;
     triggerLogIdSet.remove(triggerParam.getLogId());
     //。。。 日志记录
     // init job context
     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<Boolean> futureTask = new FutureTask<Boolean>(new Callable<Boolean>() {
        @Override
        public Boolean call() throws Exception {

         // init job context
         XxlJobContext.setXxlJobContext(xxlJobContext);
       // 执行处理器
         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
    //校验处理结果
     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 {
     ...各种线程队列交互
  // destroy 
  //调用handler的destroy方法
  try {
   handler.destroy();
  } catch (Throwable e) {
   logger.error(e.getMessage(), e);
  }

  logger.info(">>>>>>>>>>> xxl-job JobThread stoped, hashCode:{}", Thread.currentThread());
 }

这里主要是通过线程去异步执行hadler的execute方法,点开execute方法,有三个实现类,对于xxl-job支持的handler创建的模式,我们最常见的应该还是Methond的方式,就是定义一个handler,再方法上加上xxl-job提供的注解

进入MethodHandler.execute方法,就是通过反射的方式,进行执行

ini 复制代码
    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);
        }
    }

MethodJobHandler会在实例化的时候,将反射需要的参数进行赋值,xxl-job会在启动的时候,进行初始化

scss 复制代码
    public void start() {

        // init JobHandler Repository (for method)
        initJobHandlerMethodRepository(xxlJobBeanList);

        // super start
        try {
            super.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
scss 复制代码
    private void initJobHandlerMethodRepository(List<Object> xxlJobBeanList) {
        if (xxlJobBeanList==null || xxlJobBeanList.size()==0) {
            return;
        }

        // init job handler from method
        for (Object bean: xxlJobBeanList) {
            // method
            Method[] methods = bean.getClass().getDeclaredMethods();
            if (methods.length == 0) {
                continue;
            }
            for (Method executeMethod : methods) {
                XxlJob xxlJob = executeMethod.getAnnotation(XxlJob.class);
                // registry
                registJobHandler(xxlJob, bean, executeMethod);
            }

        }

    }
typescript 复制代码
    protected void registJobHandler(XxlJob xxlJob, Object bean, Method executeMethod){
        if (xxlJob == null) {
            return;
        }
          。。。初始化参数
        // registry jobhandler
        registJobHandler(name,
        // 实例化MethodJobHandler
        new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));

    }
// 将处理好的handler注册,用于后续执行的时候初始化获取
    public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){
        logger.info(">>>>>>>>>>> xxl-job register jobhandler success, name:{}, jobHandler:{}", name, jobHandler);
        return jobHandlerRepository.put(name, jobHandler);
    }    

总结

总的来说,xxl-job会在启动的时候,扫描所有注解有xxl-job的handler,并将其初始化,存入到缓存中,并在第一次加载的时候通过handler的名称进行获取,最后再通过反射的方式对对应的handler进行执行

相关推荐
陌上花开࿈3 小时前
调用第三方接口
java
Aileen_0v03 小时前
【玩转OCR | 腾讯云智能结构化OCR在图像增强与发票识别中的应用实践】
android·java·人工智能·云计算·ocr·腾讯云·玩转腾讯云ocr
桂月二二5 小时前
Java与容器化:如何使用Docker和Kubernetes优化Java应用的部署
java·docker·kubernetes
liuxin334455665 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
海绵波波1075 小时前
flask后端开发(10):问答平台项目结构搭建
后端·python·flask
小马爱打代码5 小时前
设计模式详解(建造者模式)
java·设计模式·建造者模式
栗子~~6 小时前
idea 8年使用整理
java·ide·intellij-idea
2301_801483696 小时前
Maven核心概念
java·maven
网络风云6 小时前
【魅力golang】之-反射
开发语言·后端·golang
Q_19284999066 小时前
基于Spring Boot的电影售票系统
java·spring boot·后端