XXL-JOB调度流程源码跟踪阅读

XXL-JOB调度流程源码跟踪阅读(源码V2.3.0)

官方文档:XXL-JOB官方文档

1.流程交互图

XXL-JOB调度流程源码跟踪阅读_第1张图片

2. 从调度中心调度任务到任务执行结束源码跟踪

2.1 从调度中心JobScheduleHelper查询任务开始

总体思想:当我们在调度中心控制台创建Job的时候,必须填写corm表达式,当我们创建的时候,就把下次调度时间的时间戳写入数据库中,因此调度线程会死循环的方式不断从数据库中捞出下次触发时间小于当前时间的Job任务,然后逐个触发。因为不停的死循环会导致CPU的大量损耗,为了避免CPU空转,所以每次读取数据后线程就会休眠4~5秒。因为要休眠5秒,而Job调度的单位是秒,所以可能错误这5秒的任务,导致任务延迟5秒,所以每次读取任务时都会预读5秒,把5秒后触发的任务也查询出来,然后在遍历Job任务时,分以下几种情况:

  1. 如果发现已错过调度时间则看调度过期策略,若是快速触发,则立即触发任务,并更新下次调度时间,若是什么也不做则直接更新下次调度时间。
  2. 如果并非过期,则直接触发任务,并且判断当前任务下次执行时间是否在未来5秒内,如果在5秒内就计算出未来秒的刻度,放入60秒刻度ringData循环map集合的队列中(无界list)并且刷新下次调度时间
  3. 以上两种情况都不满足则是预读数据,此刻不需要触发但是5秒内需要触发,则把Job放入ringData循环map集合的队列中并更新下次调度时间

调度线程关键源码如下

// schedule thread 调度线程
scheduleThread = new Thread(new Runnable() {
    @Override
    public void run() {

        try {
            TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 );
        } catch (InterruptedException e) {
            if (!scheduleThreadToStop) {
                logger.error(e.getMessage(), e);
            }
        }
        logger.info(">>>>>>>>> init xxl-job admin scheduler success.");

        // 预读数量,(快调度线程池+慢调度线程池的数量)*20 我猜作者是预估每次调度时间约50毫秒,那么qps就是这么算的
        // pre-read count: treadpool-size * trigger-qps (each trigger cost 50ms, qps = 1000/50 = 20)
        int preReadCount = (XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax() + XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax()) * 20;

        while (!scheduleThreadToStop) {
            // Scan Job 扫描job任务
            long start = System.currentTimeMillis();

            Connection conn = null;
            Boolean connAutoCommit = null;
            PreparedStatement preparedStatement = null;

            boolean preReadSuc = true;
            try {
                conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
                connAutoCommit = conn.getAutoCommit();
                conn.setAutoCommit(false);
                // 借助数据库实现分布式锁,这也是为调度中心做集群管理埋下铺垫,即使多调度中心也能保证只触发一次
                preparedStatement = conn.prepareStatement(  "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" );
                preparedStatement.execute();

                // tx start
                // 1、pre read 读出所有需要触发的job包括未来5秒内会触发的
                // 这里竟然不分页,直接读指定数据量,但是好像也也没法分页,哈哈
                // 这里是不是间接说明xxl-job单机不支持超过5000的调度任务,超过则会延迟5秒
                long nowTime = System.currentTimeMillis();
                List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
                if (scheduleList!=null && scheduleList.size()>0) {
                    // 2、push time-ring 遍历任务
                    for (XxlJobInfo jobInfo: scheduleList) {

                        // time-ring jump
                        if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) {
                            // 2.1、trigger-expire > 5s:pass && make next-trigger-time
                            logger.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId());
                            
                            // 过期策略匹配,看是否是立即触发
                            // 1、misfire match
                            MisfireStrategyEnum misfireStrategyEnum = MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), MisfireStrategyEnum.DO_NOTHING);
                            if (MisfireStrategyEnum.FIRE_ONCE_NOW == misfireStrategyEnum) {
                                // FIRE_ONCE_NOW 》 trigger
                                JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.MISFIRE, -1, null, null, null);
                                logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );
                            }

                            // 2、fresh next
                            refreshNextValidTime(jobInfo, new Date());
                        } else if (nowTime > jobInfo.getTriggerNextTime()) {
                            // 此时就是满足触发条件的立即触发
                            // 2.2、trigger-expire < 5s:direct-trigger && make next-trigger-time
                            // 1、trigger
                            JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
                            logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );

                            // 2、fresh next
                            refreshNextValidTime(jobInfo, new Date());
                            
                            // 顺便看一下下次触发时间是不是在未来5秒内,是则要放到循环调度队列中,以免休眠5秒错过调度时间
                            // next-trigger-time in 5s, pre-read again
                            if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {

                                // 1、make ring second
                                int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
                                // 放入定时循环调度队列中
                                // 2、push time ring
                                pushTimeRing(ringSecond, jobInfo.getId());

                                // 3、fresh next
                                refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
                            }
                        } else {
                            // 2.3、trigger-pre-read:time-ring trigger && make next-trigger-time
                            // 走到这里说明该任务是预读的任务,此时不需要触发但是未来5秒内会触发,放入循环调度队列中
                            // 1、make ring second
                            int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);

                            // 2、push time ring
                            pushTimeRing(ringSecond, jobInfo.getId());

                            // 3、fresh next
                            refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
                        }
                    }
                    // 更新数据库调度下次时间
                    // 3、update trigger info
                    for (XxlJobInfo jobInfo: scheduleList) {
                        XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
                    }
                } else {
                    preReadSuc = false;
                }
                // tx stop
            } catch (Exception e) {
                if (!scheduleThreadToStop) {
                    logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
                }
            } finally {
                // commit
                if (conn != null) {
                    try {
                        conn.commit();
                    } catch (SQLException e) {
                        if (!scheduleThreadToStop) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                    try {
                        conn.setAutoCommit(connAutoCommit);
                    } catch (SQLException e) {
                        if (!scheduleThreadToStop) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        if (!scheduleThreadToStop) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }

                // close PreparedStatement
                if (null != preparedStatement) {
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        if (!scheduleThreadToStop) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
            }
            long cost = System.currentTimeMillis()-start;
            // Wait seconds, align second
            if (cost < 1000) {  // scan-overtime, not wait
                try {
                    // pre-read period: success > scan each second; fail > skip this period;
                    TimeUnit.MILLISECONDS.sleep((preReadSuc?1000:PRE_READ_MS) - System.currentTimeMillis()%1000);
                } catch (InterruptedException e) {
                    if (!scheduleThreadToStop) {
                        logger.error(e.getMessage(), e);
                    }
                }
            }
        }
        logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
    }
});

上述会把即将触发但是还未到时间的任务放入ringData集合中,那么谁来消费这些数据呢,所以除了调度线程以外还有一个ringThread循环数据处理线程,接下来我们欣赏一下这个ringThread线程的处理逻辑。
总体思想:先对齐秒钟,也就是休眠0~1000毫秒,到了整秒的时候就计算出当前的秒数,然后从ringData这个map集合钟取出对应秒数的jobId任务集合逐个触发,避免处理时间超过1秒加上最开式1秒的对齐休眠则多处理1秒,也就是把当前秒和下一秒的任务都触发掉。

循环触发线程源码如下

// ring thread
ringThread = new Thread(new Runnable() {
    @Override
    public void run() {
        while (!ringThreadToStop) {
            // align second  秒对齐
            try {
                TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
            } catch (InterruptedException e) {
                if (!ringThreadToStop) {
                    logger.error(e.getMessage(), e);
                }
            }
            try {
                // second data 获取当前秒和下一秒的任务
                List<Integer> ringItemData = new ArrayList<>();
                int nowSecond = Calendar.getInstance().get(Calendar.SECOND);   // 避免处理耗时太长,跨过刻度,向前校验一个刻度;
                for (int i = 0; i < 2; i++) {
                    List<Integer> tmpData = ringData.remove( (nowSecond+60-i)%60 );
                    if (tmpData != null) {
                        ringItemData.addAll(tmpData);
                    }
                }
                // ring trigger  逐个触发任务
                logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
                if (ringItemData.size() > 0) {
                    // do trigger
                    for (int jobId: ringItemData) {
                        // do trigger
                        JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null);
                    }
                    // clear
                    ringItemData.clear();
                }
            } catch (Exception e) {
                if (!ringThreadToStop) {
                    logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
                }
            }
        }
        logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
    }
});

2.2 再到调度中心JobTriggerPoolHelper触发任务

上面我们已经找到了要触发的任务,并且已把任务加入触发器了,接下来我们看看触发器具体怎么触发的。就是把任务丢到线程池异步触发,避免触发网络问题导致时间过程影响调度其他任务,并且在选择线程池的时候把任务做了统计,超过500ms的任务记录下来了,这类任务超过10次,就认为是慢任务,选择线程池的时候就直接选择慢线程池。触发就是异步调用RPC接口,通知执行器执行任务,并且会告诉执行器当前任务执行日志的id,执行器执行完任务后会回调调度中心,携带该日志id,告诉调度中心该次调度的结果与执行完成的时间与结果。

添加触发器过程源码如下

/**
 * add trigger
 */
public void addTrigger(final int jobId,
                       final TriggerTypeEnum triggerType,
                       final int failRetryCount,
                       final String executorShardingParam,
                       final String executorParam,
                       final String addressList) {

    // choose thread pool
    ThreadPoolExecutor triggerPool_ = fastTriggerPool;
    AtomicInteger jobTimeoutCount = jobTimeoutCountMap.get(jobId);
    if (jobTimeoutCount!=null && jobTimeoutCount.get() > 10) {      // job-timeout 10 times in 1 min
        triggerPool_ = slowTriggerPool;
    }

    // trigger
    triggerPool_.execute(new Runnable() {
        @Override
        public void run() {

            long start = System.currentTimeMillis();

            try {
                // do trigger
                XxlJobTrigger.trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            } finally {

                // check timeout-count-map
                long minTim_now = System.currentTimeMillis()/60000;
                if (minTim != minTim_now) {
                    minTim = minTim_now;
                    jobTimeoutCountMap.clear();
                }

                // incr timeout-count-map
                long cost = System.currentTimeMillis()-start;
                if (cost > 500) {       // ob-timeout threshold 500ms
                    AtomicInteger timeoutCount = jobTimeoutCountMap.putIfAbsent(jobId, new AtomicInteger(1));
                    if (timeoutCount != null) {
                        timeoutCount.incrementAndGet();
                    }
                }

            }

        }
    });
}

2.3 再到执行器ExecutorBizImpl接收任务

上述触发器已经调用RPC接口执行器将在这个类里面收到请求,执行器收到请求后会找出该任务jobId的线程,然后放入该线程的触发参数阻塞队列中,然后异步消费阻塞队列中的任务,所以很短的时间就调度完成了,jobTread循环消费任务时,若是有超时时间则会new一个线程,等待超时时间(看到这里感觉如果任务时间很长还调度很频繁还有超时时间会导致执行器线程数量暴涨)
同一个任务会在同一个线程中排队执行
RPC接口接收任务的源码如下

@Override
public ReturnT<String> run(TriggerParam triggerParam) {
    // load old:jobHandler + jobThread
    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
        IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());

        // valid old 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
        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) {

        // 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
    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;
}

2.4 再到执行器JobThread执行任务

执行器中真正执行handle任务的线程,从任务队列中不停的取任务执行,等待执行结果,并把执行结果放入回调队列中,通过回调线程池,把job执行的结果告诉调度中心。
注意:如果你执行器重启了,那么这些执行任务调度中心将永远无法收到执行结果,因为任务在内存里,重启就丢了。
jobThread关键源码如下:

@Override
public void run() {
   // init
   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);
     if (triggerParam!=null) {
        running = true;
        idleTimes = 0;
        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 = new XxlJobContext(
              triggerParam.getJobId(),
              triggerParam.getExecutorParams(),
              logFileName,
              triggerParam.getBroadcastIndex(),
              triggerParam.getBroadcastTotal());

        // init job context
        XxlJobContext.setXxlJobContext(xxlJobContext);

        // execute
        XxlJobHelper.log("
----------- xxl-job job execute start -----------
----------- 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("
----------- 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("
----------- xxl-job job execute end(finish) -----------
----------- Result: handleCode="
+ XxlJobContext.getXxlJobContext().getHandleCode() + ", handleMsg = " + XxlJobContext.getXxlJobContext().getHandleMsg() ); } else { if (idleTimes > 30) { if(triggerQueue.size() == 0) { // avoid concurrent trigger causes jobId-lost XxlJobExecutor.removeJobThread(jobId, "excutor idel times over limit."); } } } } catch (Throwable e) { if (toStop) { XxlJobHelper.log("
----------- JobThread toStop, stopReason:"
+ stopReason); } // handle result StringWriter stringWriter = new StringWriter(); e.printStackTrace(new PrintWriter(stringWriter)); String errorMsg = stringWriter.toString(); XxlJobHelper.handleFail(errorMsg); XxlJobHelper.log("
----------- JobThread Exception:"
+ errorMsg + "
----------- 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_COCE_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_COCE_FAIL, stopReason + " [job not executed, in the job queue, killed.]") ); } } // destroy try { handler.destroy(); } catch (Throwable e) { logger.error(e.getMessage(), e); } logger.info(">>>>>>>>>>> xxl-job JobThread stoped, hashCode:{}", Thread.currentThread()); }

2.5 再到执行器TriggerCallbackThread回传执行结果

最后把执行结果回调通知调度中心,到这里一次完整的调度执行过程就结果了,这里调度执行过程里的最后一步,将结果回传给调度中心。
相关源码如下:

// callback
triggerCallbackThread = new Thread(new Runnable() {
    @Override
    public void run() {
        // normal callback
        while(!toStop){
            try {
                HandleCallbackParam callback = getInstance().callBackQueue.take();
                if (callback != null) {

                    // callback list param
                    List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
                    int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
                    callbackParamList.add(callback);

                    // callback, will retry if error
                    if (callbackParamList!=null && callbackParamList.size()>0) {
                        doCallback(callbackParamList);
                    }
                }
            } catch (Exception e) {
                if (!toStop) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
        // last callback
        try {
            List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
            int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
            if (callbackParamList!=null && callbackParamList.size()>0) {
                doCallback(callbackParamList);
            }
        } catch (Exception e) {
            if (!toStop) {
                logger.error(e.getMessage(), e);
            }
        }
        logger.info(">>>>>>>>>>> xxl-job, executor callback thread destory.");

    }
});

3. job任务注册两个重要参数说明

3.1 阻塞策略

  1. 单机串行:在同一个执行器上是按顺序执行的
  2. 丢弃最后:当发现同一个执行器上相同的job上一个还没执行完则直接丢弃当前任务
  3. 覆盖之前:当发现上一个job还没执行完,则不管之前的哪个job线程,新new一个job线程执行当前任务

3.2 路由策略

  1. 第一个:选择第一个执行器
  2. 最后一个:选择最后一个执行器
  3. 轮询:从执行器列表中轮询选择每一个执行器
  4. 分片广播:按执行器数量,每一个执行器都会被调用,但是执行器收到的分片参数不一样,执行器根据分片标志自行处理
  5. 一致性hash:通过jobId算出md5的hash值,然后从地址列表中选择一个地址调度,即使执行器增加也不会改变调度的目标执行器

当然还有很多其他路由策略,但是没怎么在实践中使用过,就不做探讨了。

4. 总结

可以看出xxl-job源码的代码量很少,作者也极力减少依赖,尽可能的使用原生写法,在调度过程中有好几步,但是每一步作者都做成了异步,每一步之间不相互影响,不等待结果而是回调通知的方式,极大的提高调度的吞吐量(有个小问题就是若执行器挂了重启后就没法回调了,因为任务在内存中,重启后就丢失了),每一步都用了独立的线程或者线程池,这也说明每个线程只做一件事做到线程之间隔离,这样就不会因为一件事慢了导致影响了其他事情的执行。源码很精简,非常值得学习。

你可能感兴趣的:(java)