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进行执行

相关推荐
爬山算法13 分钟前
Maven(28)如何使用Maven进行依赖解析?
java·maven
hlsd#20 分钟前
go mod 依赖管理
开发语言·后端·golang
陈大爷(有低保)24 分钟前
三层架构和MVC以及它们的融合
后端·mvc
亦世凡华、25 分钟前
【启程Golang之旅】从零开始构建可扩展的微服务架构
开发语言·经验分享·后端·golang
河西石头26 分钟前
一步一步从asp.net core mvc中访问asp.net core WebApi
后端·asp.net·mvc·.net core访问api·httpclient的使用
2401_8574396937 分钟前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧66639 分钟前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
李老头探索40 分钟前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
芒果披萨1 小时前
Filter和Listener
java·filter
qq_4924484461 小时前
Java实现App自动化(Appium Demo)
java