xxl-job任務觸發流程RemoteHttpJobBean到callback以及執行器的註冊流程(轉載)

RemotehttpJobBean 觸發任務源碼分析

xxl-job 所有的任務觸發最終都是通過這個類來執行 , 該類繼承關係如下:

RemoteHttpJobBean > QuartzJobBean > Job
當quartz監聽到有任務需要觸發是,會調用 JobRunShell 的run方法, 在該類的run方法中,會調用當前任務的JOB_CLASS 的excute方法,

調用鏈最終會調用到remoteHttpJobBean 的 executeInternal()

 

@Override
protected void executeInternal(JobExecutionContext context)
      throws JobExecutionException {

   // load jobId
   JobKey jobKey = context.getTrigger().getJobKey();
   Integer jobId = Integer.valueOf(jobKey.getName());

   // trigger
   XxlJobTrigger.trigger(jobId); // 詳細的代碼分析往下看
}
public static void trigger(int jobId) {

    // 通過JobId從數據庫中查詢該任務的具體信息
    XxlJobInfo jobInfo = XxlJobDynamicScheduler.xxlJobInfoDao.loadById(jobId);              // job info
    if (jobInfo == null) {
        logger.warn(">>>>>>>>>>>> trigger fail, jobId invalid,jobId={}", jobId);
        return;
    }
    // 獲取該類型的執行器信息
    XxlJobGroup group = XxlJobDynamicScheduler.xxlJobGroupDao.load(jobInfo.getJobGroup());  // group info
     
    // 匹配運行模式
    ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), ExecutorBlockStrategyEnum.SERIAL_EXECUTION);  // block strategy
    // 匹配失敗後的處理模式
    ExecutorFailStrategyEnum failStrategy = ExecutorFailStrategyEnum.match(jobInfo.getExecutorFailStrategy(), ExecutorFailStrategyEnum.FAIL_ALARM);    // fail strategy
    //  獲取路由策略
    ExecutorRouteStrategyEnum executorRouteStrategyEnum = ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null);    // route strategy
    // 獲取該執行器的集羣機器列表
    ArrayList<String> addressList = (ArrayList<String>) group.getRegistryList();
     
    // 判斷路由策略  是否爲  分片廣播模式
    if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum && CollectionUtils.isNotEmpty(addressList)) {
        for (int i = 0; i < addressList.size(); i++) {
            String address = addressList.get(i);
            //定義日誌信息
            XxlJobLog jobLog = new XxlJobLog();
            // .....省略
            ReturnT<String> triggerResult = new ReturnT<String>(null);
     
            if (triggerResult.getCode() == ReturnT.SUCCESS_CODE) {
                // 4.1、trigger-param
                TriggerParam triggerParam = new TriggerParam();
                triggerParam.setJobId(jobInfo.getId());
                triggerParam.setBroadcastIndex(i); // 設置分片標記
                triggerParam.setBroadcastIndex(addressList.size());// 設置分片總數
                // ......省略組裝參數的過程
     
                // 根據參數以及 機器地址,向執行器發送執行信息 , 此處將會詳細講解runExecutor 這個方法
                triggerResult = runExecutor(triggerParam, address);
            }
            // 將日誌ID,放入隊列,便於日誌監控線程來監控任務的執行狀態
            JobFailMonitorHelper.monitor(jobLog.getId());
            logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
     
        }
    } else {
        // 出分片模式外,其他的路由策略均走這裏
        //定義日誌信息
        XxlJobLog jobLog = new XxlJobLog();
        jobLog.setJobGroup(jobInfo.getJobGroup());
        // .....省略
        ReturnT<String> triggerResult = new ReturnT<String>(null);
        if (triggerResult.getCode() == ReturnT.SUCCESS_CODE) {
            // 4.1、trigger-param
            TriggerParam triggerParam = new TriggerParam();
            triggerParam.setJobId(jobInfo.getId());
            triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
            triggerParam.setBroadcastIndex(0); // 默認分片標記爲0
            triggerParam.setBroadcastTotal(1);  // 默認分片總數爲1
            // .... 省略組裝參數的過程
            // 此處使用了策略模式, 根據不同的策略 使用不同的實現類,此處不再詳細說明
            triggerResult = executorRouteStrategyEnum.getRouter().routeRun(triggerParam, addressList);
        }
        JobFailMonitorHelper.monitor(jobLog.getId());
        logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
    }
}

繼續往下面看, 着重看 runExecutor 這個方法 , 向執行器發送指令都是從這個方法中執行的

 

public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address){
    ReturnT<String> runResult = null;
    try {
        //創建一個ExcutorBiz 的對象,重點在這個方法裏面
        ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
        // 這個run 方法不會最終執行,僅僅只是爲了觸發 proxy object 的 invoke方法,同時將目標的類型傳送給服務端, 因爲在代理對象的invoke的方法裏面沒有執行目標對象的方法
        runResult = executorBiz.run(triggerParam);
    } catch (Exception e) {
        logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
        runResult = new ReturnT<String>(ReturnT.FAIL_CODE, ""+e );
    }
     
    StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + ":");
    runResultSB.append("<br>address:").append(address);
    runResultSB.append("<br>code:").append(runResult.getCode());
    runResultSB.append("<br>msg:").append(runResult.getMsg());
     
    runResult.setMsg(runResultSB.toString());
    runResult.setContent(address);
    return runResult;
}

通過上面可知, XxlJobDynamicScheduler.getExecutorBiz (address) , 通過機器地址,獲取一個executor , 看下他的源碼。

 

public static ExecutorBiz getExecutorBiz(String address) throws Exception {
    // valid
    if (address==null || address.trim().length()==0) {
        return null;
    }
     
    // load-cache
    address = address.trim();
    //查看緩存裏面是否存在,如果存在則不需要再去創建executorBiz了
    ExecutorBiz executorBiz = executorBizRepository.get(address);
    if (executorBiz != null) {
        return executorBiz;
    }
     
    // 創建ExecutorBiz的代理對象,重點在這個裏面。
    executorBiz = (ExecutorBiz) new NetComClientProxy(ExecutorBiz.class, address, accessToken).getObject();
    executorBizRepository.put(address, executorBiz);
    return executorBiz;
}

NetComClientProxy 這是一個factoryBean , 所以我們主要看他的getObject 方法就知道怎麼創建對象並返回的。
下面這個代理對象的invoke裏面並沒有執行目標類的方法,而是將目標類的信息包裝好,發送給執行器那一端來做。

 

//NetComCliendProxy
public Object getObject() throws Exception {
   return Proxy.newProxyInstance(Thread.currentThread()
         .getContextClassLoader(), new Class[] { iface },
         new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                 
               if (Object.class.getName().equals(method.getDeclaringClass().getName())) {
                  logger.error(">>>>>>>>>>> xxl-rpc proxy class-method not support [{}.{}]", method.getDeclaringClass().getName(), method.getName());
                  throw new RuntimeException("xxl-rpc proxy class-method not support");
               }
                
               // 重點來了,創建request信息, 發送HTTP請求到執行器服務器上。
               RpcRequest request = new RpcRequest();
                    request.setServerAddress(serverAddress); // 服務器地址
                    request.setCreateMillisTime(System.currentTimeMillis()); // 創建時間, 用於判斷請求是否超時
                    request.setAccessToken(accessToken);  // 數據校驗
                    request.setClassName(method.getDeclaringClass().getName()); // 將目標類的class名稱傳給執行器,讓那邊來創建對象,並執行邏輯代碼
                    request.setMethodName(method.getName());   // 方法名稱爲run 
                    request.setParameterTypes(method.getParameterTypes());  // 參數類型
                    request.setParameters(args); // 參數
                    RpcResponse response = client.send(request); // 發送HTTP請求
               if (response == null) {
                  logger.error(">>>>>>>>>>> xxl-rpc netty response not found.");
                  throw new Exception(">>>>>>>>>>> xxl-rpc netty response not found.");
                }
                    if (response.isError()) {
                        throw new RuntimeException(response.getError());
                    } else {
                        // 返回請求結果
                        return response.getResult();
                    }
                    
            }
         });
}

以上就是調度中心,觸發任務之後執行的核心代碼 , 接下來繼續分析執行器服務端接收到請求之後的處理邏輯

執行器啓動源碼分析

服務端應用裏面,實際上是在應用中,內嵌了一個jetty服務器, 服務器在xxlJobExecutor 初始化的時候啓動。

本次示例代碼中是由spring-boot 中截取而來, 該項目中,由XxlJobConfig 這個配置類來配置Executor

 

@Bean(initMethod = "start", destroyMethod = "destroy")
public XxlJobExecutor xxlJobExecutor() {
    logger.info(">>>>>>>>>>> xxl-job config init.");
    XxlJobExecutor xxlJobExecutor = new XxlJobExecutor();
    xxlJobExecutor.setAdminAddresses(adminAddresses);
    xxlJobExecutor.setAppName(appName);
    xxlJobExecutor.setIp(ip);
    xxlJobExecutor.setPort(port);
    xxlJobExecutor.setAccessToken(accessToken);
    xxlJobExecutor.setLogPath(logPath);
    xxlJobExecutor.setLogRetentionDays(logRetentionDays);
    
    return xxlJobExecutor;
}

由上面可以看出,初始化 XxlJobExecutor 這個bean之後,會默認執行start 方法

 

public void start() throws Exception {
    // 初始化調度中心的地址列表, 通過NetComClientProxy創建好adminBiz實例
    initAdminBizList(adminAddresses, accessToken);
     
    // 初始化所有帶有@JobHandler的handle, 根據name , 放入一個ConcurrentHashMap 中
    initJobHandlerRepository(applicationContext);
     
    // 初始化本地日誌路徑
    XxlJobFileAppender.initLogPath(logPath);
     
    // 初始化本地jetty服務器
    initExecutorServer(port, ip, appName, accessToken);
     
    // 啓動一個線程,用來清理本地日誌, 默認保留最近一天的日誌
    JobLogFileCleanThread.getInstance().start(logRetentionDays);
}
上面初始化的那些方法,着重看 initExecutorServer () 這個方法

private void initExecutorServer(int port, String ip, String appName, String accessToken) throws Exception {
    // 如果port爲空,則默認9999爲他的jetty服務器端口
    port = port>0?port: NetUtil.findAvailablePort(9999);
     
    // 創建一個ExecutorService 實例,放入Map中,後面會通過class獲取到他的實例執行run方法
    NetComServerFactory.putService(ExecutorBiz.class, new ExecutorBizImpl());   // rpc-service, base on jetty
    NetComServerFactory.setAccessToken(accessToken);
    // 啓動jetty 服務器
    serverFactory.start(port, ip, appName); // jetty + registry
}
JettyServer
public void start(final int port, final String ip, final String appName) throws Exception {
   thread = new Thread(new Runnable() {
      @Override
      public void run() {
     
         // The Server
         server = new Server(new ExecutorThreadPool());  // 非阻塞
         // HTTP connector
         ServerConnector connector = new ServerConnector(server);
         if (ip!=null && ip.trim().length()>0) {
            connector.setHost(ip); // The network interface this connector binds to as an IP address or a hostname.  If null or 0.0.0.0, then bind to all interfaces.
         }
         connector.setPort(port);
        // 設置連接器
         server.setConnectors(new Connector[]{connector});
     
         // 設置一個連接處理的handler
         HandlerCollection handlerc =new HandlerCollection();
         handlerc.setHandlers(new Handler[]{new JettyServerHandler()});
         server.setHandler(handlerc);
     
         try {
            // Start server
            server.start();
            logger.info(">>>>>>>>>>> xxl-job jetty server start success at port:{}.", port);
     
            // 此處是啓動一個執行器註冊的線程, 該線程第一次執行的時候,將該執行器的信息註冊到數據庫, xxl_job_qrtz_trigger_registry 這張表中  ,
            // 此後,沒過30秒, 執行器就會去數據庫更新數據,表示自己還在存活中
            // 調度中心那邊會有一個線程定期的去數據庫掃描,會自動的將30秒之內未更新信息的機器剔除, 同時將新加入的服務載入到集羣列表中
            ExecutorRegistryThread.getInstance().start(port, ip, appName);
     
            // 啓動一個日誌監控的線程,裏面設置了一個隊列,每次有任務結束後,都會把任務的日誌ID和處理結果放入隊列,
            // 線程從隊列裏面拿到日誌ID和處理結果,通過調用adminBiz的callback方法來回調給調度中心執行結果
            TriggerCallbackThread.getInstance().start();
     
            server.join(); // block until thread stopped
            logger.info(">>>>>>>>>>> xxl-rpc server join success, netcon={}, port={}", JettyServer.class.getName(), port);
         } catch (Exception e) {
            logger.error(e.getMessage(), e);
         } finally {
            //destroy();
         }
      }
   });
   thread.setDaemon(true);    // daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave
   thread.start();
}

JettyServerHandler 接收請求後的處理流程

 

//JettyServerHandler
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {    
   // invoke  主要在這個方法
   RpcResponse rpcResponse = doInvoke(request);

  // serialize response
  byte[] responseBytes = HessianSerializer.serialize(rpcResponse);  
   response.setContentType("text/html;charset=utf-8");
   response.setStatus(HttpServletResponse.SC_OK);
   baseRequest.setHandled(true);
    // 響應結果
   OutputStream out = response.getOutputStream();
   out.write(responseBytes);
   out.flush();  
}

通過上面的handle中的代碼可以知道,主要的執行邏輯在doInvoke 的方法中,

 

private RpcResponse doInvoke(HttpServletRequest request) {
   try {
      // deserialize request
      // 讀取請求數據
      byte[] requestBytes = HttpClientUtil.readBytes(request);
      if (requestBytes == null || requestBytes.length==0) {
         RpcResponse rpcResponse = new RpcResponse();
         rpcResponse.setError("RpcRequest byte[] is null");
         return rpcResponse;
      }
      // 通過hessian的序列化機制,反序列化得到字符串數據
      RpcRequest rpcRequest = (RpcRequest) HessianSerializer.deserialize(requestBytes, RpcRequest.class);
     
        // 得到參數之後, 執行
        RpcResponse rpcResponse = NetComServerFactory.invokeService(rpcRequest, null);
        return rpcResponse;
     } catch (Exception e) {
      logger.error(e.getMessage(), e);
     
      RpcResponse rpcResponse = new RpcResponse();
      rpcResponse.setError("Server-error:" + e.getMessage());
      return rpcResponse;
   }
}

 

//NetComServerFactory
public static RpcResponse invokeService(RpcRequest request, Object serviceBean) {
   // request中的數據結構,可以看上面源碼分析中提到的 NetComClientProxy中的getObjct 方法,此處不再贅述
   if (serviceBean==null) {
      //  這個serviceBean 就是在執行器啓動的時候,initExecutorServer () 這個方法中,將一個ExecutorBiz的實例放進去了,此處通過
      // classname來獲取這個實例
      serviceBean = serviceMap.get(request.getClassName());
   }
   if (serviceBean == null) {
      // TODO
   }

   RpcResponse response = new RpcResponse();
  // 判斷是否超時
   if (System.currentTimeMillis() - request.getCreateMillisTime() > 180000) {
      response.setResult(new ReturnT<String>(ReturnT.FAIL_CODE, "The timestamp difference between admin and executor exceeds the limit."));
      return response;
   }
    // 數據校驗,驗證token是否匹配,前提是token不爲空
   if (accessToken!=null && accessToken.trim().length()>0 && !accessToken.trim().equals(request.getAccessToken())) {
      response.setResult(new ReturnT<String>(ReturnT.FAIL_CODE, "The access token[" + request.getAccessToken() + "] is wrong."));
      return response;
   }

   try {
      // 獲取class
      Class<?> serviceClass = serviceBean.getClass();
      // 拿到請求中的方法名字, 此處這個值 是  run 方法
      String methodName = request.getMethodName();
        //方法類型
      Class<?>[] parameterTypes = request.getParameterTypes();
      // 方法參數
      Object[] parameters = request.getParameters();
      // spring的工具類, 創建一個fastClass 實例
      FastClass serviceFastClass = FastClass.create(serviceClass);
      FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, parameterTypes);
       // 拿到方法之後執行方法的invoke ,
      Object result = serviceFastMethod.invoke(serviceBean, parameters);
     
      response.setResult(result);
   } catch (Throwable t) {
      t.printStackTrace();
      response.setError(t.getMessage());
   }

   return response;
}

通過調度中心發過來的參數,以及執行器的處理邏輯,我們有理由可以得出此時是執行的是ExecutorBizImpl中的run方法

 

//ExecutorBizImpl
@Override
public ReturnT<String> run(TriggerParam triggerParam) {
    // 通過參數中的JobID, 從本地線程庫裏面獲取線程 ( 第一次進來是沒有線程的,jobThread爲空 ,
    // 如果線程運行90秒空閒之後,那麼也會被移除)
    // 本地線程庫,本質上就是一個ConcurrentHashMap<Integer, JobThread>
    JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId());
    IJobHandler jobHandler = jobThread!=null?jobThread.getHandler():null;
    String removeOldReason = null;
     
    //匹配任務類型, BEAN是我們自定義JOBHANDLE的模式
    GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType());
    if (GlueTypeEnum.BEAN == glueTypeEnum) {
     
        // 通過參數中的handlerName從本地內存中獲取handler實例 (在執行器啓動的時候,是把所有帶有@JobHandler的實例通過name放入到一個map中的 )
        IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());
     
        // 如果修改了任務的handler, name此處會默認把以前老的handler清空,後面會以最新的newJobHandler爲準
        if (jobThread!=null && jobHandler != newJobHandler) {
            // change handler, need kill old thread
            removeOldReason = "更換JobHandler或更換任務模式,終止舊任務線程";
     
            jobThread = null;
            jobHandler = null;
        }
     
        // valid handler
        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) {
        // .... 省略一段代碼
        // 此處說的是,任務模式爲 GLUE JAVA版, 最後是通過GROOVY的方式,將代碼生成class類,最終執行,最終原理和上面一直
         
    } else if (glueTypeEnum!=null && glueTypeEnum.isScript()) {
     
       // 其他腳本執行模式,shell , python等
    } 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) {
            // 這種阻塞策略說的是,丟棄後續調度, 如果這個線程正在執行的話,那麼當前這個任務就不執行了,直接返回
            if (jobThread.isRunningOrHasQueue()) {
                return new ReturnT<String>(ReturnT.FAIL_CODE, "阻塞處理策略-生效:"+ExecutorBlockStrategyEnum.DISCARD_LATER.getTitle());
            }
        } else if (ExecutorBlockStrategyEnum.COVER_EARLY == blockStrategy) {
            // 覆蓋之前的調度, 如果當前線程以及執行了的話,那麼中斷這個線程, 直接將jobThread銷燬
            if (jobThread.isRunningOrHasQueue()) {
                removeOldReason = "阻塞處理策略-生效:" + ExecutorBlockStrategyEnum.COVER_EARLY.getTitle();
     
                jobThread = null;
            }
        } else {
            // just queue trigger
        }
    }
     
    // 如果jobThread爲空,那麼這個時候,就要註冊一個線程到本地線程庫裏面去。 同時啓動這個線程。
    if (jobThread == null) {
        jobThread = XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason);
    }
     
    // 將本次任務的參數 ,放入到隊列裏面去,供線程調度。
    ReturnT<String> pushResult = jobThread.pushTriggerQueue(triggerParam);
    return pushResult;
}

通過上面我們可以發現, 執行executorBiz的run 方法的時候, 首先會通過JOBID,從本地線程庫裏面獲取該任務對應的線程,同時,如果任務的JobHandler有更新的話,

那麼會自動使用最新的jobHandler , 同時根據任務的阻塞策略。 執行不同的操作。 最終,如果是第一次執行任務的時候,系統會分配給改任務一個線程,同時啓動該線程。

接下來,可以在具體看一下JobThread 的run方法,看下最終的任務是如何執行的。

 

//JobThread
@Override
public void run() {

       // init
  try {
     // 執行IJobHandler 中的init方法,以後如果有一些,在執行handler之前的初始化的工作,可以覆寫這個方法
      handler.init();
   } catch (Throwable e) {
          logger.error(e.getMessage(), e);
   }

   // stop 爲fasle的時候執行
   while(!toStop){
          running = false;
         // 執行次數
         idleTimes++;
     
           TriggerParam triggerParam = null;
           ReturnT<String> executeResult = null;
           try {
         // to check toStop signal, we need cycle, so wo cannot use queue.take(), instand of poll(timeout)
         // 從linkBlockingQueue中獲取數據,如果3秒獲取不到,則返回null
         triggerParam = triggerQueue.poll(3L, TimeUnit.SECONDS);
         if (triggerParam!=null) {
            running = true;
            // 將運行次數清空,保證運行90秒空閒之後會被移除
            idleTimes = 0;
            // 獲取數據
            triggerLogIdSet.remove(triggerParam.getLogId());
     
            // log filename, like "logPath/yyyy-MM-dd/9999.log"
            // 創建日誌
            String logFileName = XxlJobFileAppender.makeLogFileName(new Date(triggerParam.getLogDateTim()), triggerParam.getLogId());
            XxlJobFileAppender.contextHolder.set(logFileName);
            // 寫入分片信息, 將當前機器的分片標記和分片總數寫入到ShardingUtil中,到時候,可以在handler中通過這個工具類獲取
            ShardingUtil.setShardingVo(new ShardingUtil.ShardingVO(triggerParam.getBroadcastIndex(), triggerParam.getBroadcastTotal()));
     
            // execute
            XxlJobLogger.log("<br>----------- xxl-job job execute start -----------<br>----------- Param:" + triggerParam.getExecutorParams());
            // 執行。。。
            executeResult = handler.execute(triggerParam.getExecutorParams());
            if (executeResult == null) {
               executeResult = IJobHandler.FAIL;
            }
            XxlJobLogger.log("<br>----------- xxl-job job execute end(finish) -----------<br>----------- ReturnT:" + executeResult);
     
         } else {
            if (idleTimes > 30) {
                // 每3秒獲取一次數據,獲取30次都沒有獲取到數據之後,則現場被清除
               XxlJobExecutor.removeJobThread(jobId, "excutor idel times over limit.");
            }
         }
      } catch (Throwable e) {
         if (toStop) {
            XxlJobLogger.log("<br>----------- JobThread toStop, stopReason:" + stopReason);
         }
     
         StringWriter stringWriter = new StringWriter();
         e.printStackTrace(new PrintWriter(stringWriter));
         String errorMsg = stringWriter.toString();
         executeResult = new ReturnT<String>(ReturnT.FAIL_CODE, errorMsg);
     
         XxlJobLogger.log("<br>----------- JobThread Exception:" + errorMsg + "<br>----------- xxl-job job execute end(error) -----------");
      } finally {
               if(triggerParam != null) {
                   // callback handler info
                   if (!toStop) {
                       // handler執行完成之後,將結果寫入到日誌裏面去, 就是在執行器啓動的時候,會建立一個線程,用來實時處理日誌,此處是將結果和logID放入到隊列裏面去,
                        //  讓日誌線程異步的去處理
                       TriggerCallbackThread.pushCallBack(new HandleCallbackParam(triggerParam.getLogId(), executeResult));
                   } else {
                       // is killed
                       ReturnT<String> stopResult = new ReturnT<String>(ReturnT.FAIL_CODE, stopReason + " [業務運行中,被強制終止]");
                       TriggerCallbackThread.pushCallBack(new HandleCallbackParam(triggerParam.getLogId(), stopResult));
                   }
               }
           }
       }

   // 當現場被終止之後,隊列裏面剩餘的未執行的任務,將被終止的這些任務放入隊列,供日誌監控線程來處理,回調給調度中心
   while(triggerQueue !=null && triggerQueue.size()>0){
      TriggerParam triggerParam = triggerQueue.poll();
      if (triggerParam!=null) {
         // is killed
         ReturnT<String> stopResult = new ReturnT<String>(ReturnT.FAIL_CODE, stopReason + " [任務尚未執行,在調度隊列中被終止]");
         TriggerCallbackThread.pushCallBack(new HandleCallbackParam(triggerParam.getLogId(), stopResult));
      }
   }

   // destroy
   try {
      handler.destroy();
   } catch (Throwable e) {
      logger.error(e.getMessage(), e);
   }

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

最後來看一下,TriggerCallbackThread.pushCallBack ()這個方法,將本次任務記錄的日誌ID和處理結果放入隊列中去了,

 

//TriggerCallbackThread
private void doCallback(List<HandleCallbackParam> callbackParamList){
    // 獲取調度中心的adminBiz列表,在執行器啓動的時候,初始化的,
    for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
        try {
            // 這裏的adminBiz 調用的callback方法,因爲是通過NetComClientProxy 這個factoryBean創建的代理對象,
            // 在getObject方法中,最終是沒有調用的目標類方法的invoke的。  只是將目標類的方法名,參數,類名,等信息發送給調度中心了
            // 發送的地址調度中心的接口地址是 :“調度中心IP/api” 這個接口 。 這個是在執行器啓動的時候初始化設置好的。
            // 調度中心的API接口拿到請求之後,通過參數裏面的類名,方法,參數,反射出來一個對象,然後invoke, 最終將結果寫入數據庫
            ReturnT<String> callbackResult = adminBiz.callback(callbackParamList);
            if (callbackResult!=null && ReturnT.SUCCESS_CODE == callbackResult.getCode()) {
                callbackResult = ReturnT.SUCCESS;
                // 因爲調度中心是集羣式的,所以只要有一臺機器返回success,那麼就算成功,直接break
                logger.info(">>>>>>>>>>> xxl-job callback success, callbackParamList:{}, callbackResult:{}", new Object[]{callbackParamList, callbackResult});
                break;
            } else {
                logger.info(">>>>>>>>>>> xxl-job callback fail, callbackParamList:{}, callbackResult:{}", new Object[]{callbackParamList, callbackResult});
            }
        } catch (Exception e) {
            logger.error(">>>>>>>>>>> xxl-job callback error, callbackParamList:{}", callbackParamList, e);
            //getInstance().callBackQueue.addAll(callbackParamList);
        }
    }
}

以上就是XXL-JOB從調用到處理的核心代碼分析。



作者:daringzyh
鏈接:https://www.jianshu.com/p/d75800b5c9f9
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章