xxl-job源码解析

目录

一、调度中心xxl-job-admin

1、Scheduler实例化

2、任务启动

3、任务执行-分片执行&路由执行策略

4、任务完成

5、任务结果丢失处理

二、执行器xxl-job-core

1、XxlJob实例化

2、初始化XxlJobExecutor

3、日志完成结果回调

4、XXL-RPC及ExecutorRegistryThread实例化


考照v2.3.0 Release源码

image.png

一、调度中心xxl-job-admin

1、Scheduler实例化

启动时通过XxlJobAdminConfig对XxlJobScheduler进行实例化

    @Override
    public void afterPropertiesSet() throws Exception {
        adminConfig = this;

        xxlJobScheduler = new XxlJobScheduler();
        xxlJobScheduler.init();
    }

XxlJobScheduler中包含

  • JobTriggerPoolHelper:定时器线程池,基础线程池
  • JobRegistryHelper:注册线程池,通过拉取xxl_job_group配置进行任务执行器注册中心初始化
  • JobFailMonitorHelper:日志线程池
  • JobCompleteHelper:任务结果处理线程池,depend on JobTriggerPoolHelper 
  • JobLogReportHelper:日志导出线程池
  • JobScheduleHelper:任务执行线程池,depend on JobTriggerPoolHelper 
public void init() throws Exception {
        // init i18n
        initI18n();

        // admin trigger pool start
        JobTriggerPoolHelper.toStart();

        // admin registry monitor run
        JobRegistryHelper.getInstance().start();

        // admin fail-monitor run
        JobFailMonitorHelper.getInstance().start();

        // admin lose-monitor run ( depend on JobTriggerPoolHelper )
        JobCompleteHelper.getInstance().start();

        // admin log report start
        JobLogReportHelper.getInstance().start();

        // start-schedule  ( depend on JobTriggerPoolHelper )
        JobScheduleHelper.getInstance().start();

        logger.info(">>>>>>>>> init xxl-job admin success.");
    }

2、任务启动

前端新建任务后,点击启动调用。

    @Override
    public ReturnT<String> start(int id) {
        XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);

        // valid
        ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(xxlJobInfo.getScheduleType(), ScheduleTypeEnum.NONE);
        if (ScheduleTypeEnum.NONE == scheduleTypeEnum) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type_none_limit_start")) );
        }

        // next trigger time (5s后生效,避开预读周期)
    //通过nextTriggerTime来触发一个次定时执行任务
        long nextTriggerTime = 0;
        try {
            Date nextValidTime = JobScheduleHelper.generateNextValidTime(xxlJobInfo, new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
            if (nextValidTime == null) {
                return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
            }
            nextTriggerTime = nextValidTime.getTime();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
        }

        xxlJobInfo.setTriggerStatus(1);
        xxlJobInfo.setTriggerLastTime(0);
        xxlJobInfo.setTriggerNextTime(nextTriggerTime);

        xxlJobInfo.setUpdateTime(new Date());
        xxlJobInfoDao.update(xxlJobInfo);
        return ReturnT.SUCCESS;
    }

然后JobScheduleHelper进行任务的调度处理

//启动手动comomit事务
conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
connAutoCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
//依赖mysql悲观锁实现分布式锁 for update
preparedStatement = conn.prepareStatement(  "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" );
preparedStatement.execute();

// tx start

// 1、pre read
long nowTime = System.currentTimeMillis();
//取出要执行的任务
List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
...
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());

                //调度状态:0-停止,1-运行
        //在执行中,且已经超过5s
        // next-trigger-time in 5s, pre-read again
        if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
        
            若任务下一次触发时间是在5秒内,则放到时间轮内(Map<Integer, List<Integer>> 秒数(1-60) => 任务id列表)
            // 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

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

    }

}

时间轮:ring线程处理逻辑

//从时间轮内移出当前秒数前2个秒数(避免处理耗时太长,跨过刻度,向前校验一个刻度)的任务列表id,一一触发任务;
// 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();
}

3、任务执行-分片执行&路由执行策略

分片执行:

  • 拉出任务的执行机器列表,逐个设置index / total,把index / total分发到任务执行器
  • 任务执行器可根据index / total参数开发分片任务
// param
ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), ExecutorBlockStrategyEnum.SERIAL_EXECUTION);  // block strategy
ExecutorRouteStrategyEnum executorRouteStrategyEnum = ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null);    // route strategy
String shardingParam = (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==executorRouteStrategyEnum)?String.valueOf(index).concat("/").concat(String.valueOf(total)):null;

// 1、save log-id
XxlJobLog jobLog = new XxlJobLog();
jobLog.setJobGroup(jobInfo.getJobGroup());
jobLog.setJobId(jobInfo.getId());
jobLog.setTriggerTime(new Date());
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().save(jobLog);
logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getId());

// 2、init trigger-param
TriggerParam triggerParam = new TriggerParam();
triggerParam.setJobId(jobInfo.getId());
triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
triggerParam.setExecutorParams(jobInfo.getExecutorParam());
triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
triggerParam.setExecutorTimeout(jobInfo.getExecutorTimeout());
triggerParam.setLogId(jobLog.getId());
triggerParam.setLogDateTime(jobLog.getTriggerTime().getTime());
triggerParam.setGlueType(jobInfo.getGlueType());
triggerParam.setGlueSource(jobInfo.getGlueSource());
triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime());
triggerParam.setBroadcastIndex(index);
triggerParam.setBroadcastTotal(total);

//分片任务执行
// 3、init address
String address = null;
ReturnT<String> routeAddressResult = null;
if (group.getRegistryList()!=null && !group.getRegistryList().isEmpty()) {
    if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) {
        if (index < group.getRegistryList().size()) {
            address = group.getRegistryList().get(index);
        } else {
            address = group.getRegistryList().get(0);
        }
    } else {
    //执行器路由策略
        routeAddressResult = executorRouteStrategyEnum.getRouter().route(triggerParam, group.getRegistryList());
        if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) {
            address = routeAddressResult.getContent();
        }
    }
} else {
    routeAddressResult = new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobconf_trigger_address_empty"));
}


// 4、trigger remote executor
ReturnT<String> triggerResult = null;
if (address != null) {
    triggerResult = runExecutor(triggerParam, address);
} else {
    triggerResult = new ReturnT<String>(ReturnT.FAIL_CODE, null);
}

// 5、collection trigger info
StringBuffer triggerMsgSb = new StringBuffer();
triggerMsgSb.append(I18nUtil.getString("jobconf_trigger_type")).append(":").append(triggerType.getTitle());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_admin_adress")).append(":").append(IpUtil.getIp());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regtype")).append(":")
        .append( (group.getAddressType() == 0)?I18nUtil.getString("jobgroup_field_addressType_0"):I18nUtil.getString("jobgroup_field_addressType_1") );
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regaddress")).append(":").append(group.getRegistryList());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorRouteStrategy")).append(":").append(executorRouteStrategyEnum.getTitle());
if (shardingParam != null) {
    triggerMsgSb.append("("+shardingParam+")");
}
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorBlockStrategy")).append(":").append(blockStrategy.getTitle());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_timeout")).append(":").append(jobInfo.getExecutorTimeout());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorFailRetryCount")).append(":").append(finalFailRetryCount);

triggerMsgSb.append("<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_run") +"<<<<<<<<<<< </span><br>")
        .append((routeAddressResult!=null&&routeAddressResult.getMsg()!=null)?routeAddressResult.getMsg()+"<br><br>":"").append(triggerResult.getMsg()!=null?triggerResult.getMsg():"");

// 6、save log trigger-info
jobLog.setExecutorAddress(address);
jobLog.setExecutorHandler(jobInfo.getExecutorHandler());
jobLog.setExecutorParam(jobInfo.getExecutorParam());
jobLog.setExecutorShardingParam(shardingParam);
jobLog.setExecutorFailRetryCount(finalFailRetryCount);
//jobLog.setTriggerTime();
jobLog.setTriggerCode(triggerResult.getCode());
jobLog.setTriggerMsg(triggerMsgSb.toString());
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(jobLog);

logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());

路由执行策略

  • 执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等;
  • 第一个、最后一个、轮询、随机:都是简单读address_list即可
  • 一致性HASH:TreeSet实现一致性hash算法
  • 最不经常使用、最近最久未使用:HashMap、LinkedHashMap
  • 故障转移:遍历address_list获取address时,逐个检查该address的心跳(请求返回状态);只有心跳正常的address才返回使用
  • 忙碌转移:遍历address_list获取address时,逐个检查该address是否忙碌(请求返回状态);只有状态为idle的address才返回使用

image.png

4、任务完成

通过JobApiController暴露apI用于在xxl-job-core中TriggerCallbackThread.doCallback调用来对任务进行完成

JobCompleteHelper中

正常完成:

private ReturnT<String> callback(HandleCallbackParam handleCallbackParam) {
        // valid log item
        XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(handleCallbackParam.getLogId());
        if (log == null) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "log item not found.");
        }
        if (log.getHandleCode() > 0) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "log repeate callback.");     // avoid repeat callback, trigger child job etc
        }

        // handle msg
        StringBuffer handleMsg = new StringBuffer();
        if (log.getHandleMsg()!=null) {
            handleMsg.append(log.getHandleMsg()).append("<br>");
        }
        if (handleCallbackParam.getHandleMsg() != null) {
            handleMsg.append(handleCallbackParam.getHandleMsg());
        }

        // success, save log
        log.setHandleTime(new Date());
        log.setHandleCode(handleCallbackParam.getHandleCode());
        log.setHandleMsg(handleMsg.toString());
        XxlJobCompleter.updateHandleInfoAndFinish(log);

        return ReturnT.SUCCESS;
    }

5、任务结果丢失处理

monitorThread中对任务结果丢失处理:调度记录停留在 "运行中" 状态超过10min,且对应执行器心跳注册失败不在线,则将本地调度主动标记失败

// 任务结果丢失处理:调度记录停留在 "运行中" 状态超过10min,且对应执行器心跳注册失败不在线,则将本地调度主动标记失败;
Date losedTime = DateUtil.addMinutes(new Date(), -10);
List<Long> losedJobIds  = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findLostJobIds(losedTime);

if (losedJobIds!=null && losedJobIds.size()>0) {
    for (Long logId: losedJobIds) {

        XxlJobLog jobLog = new XxlJobLog();
        jobLog.setId(logId);

        jobLog.setHandleTime(new Date());
        jobLog.setHandleCode(ReturnT.FAIL_CODE);
        jobLog.setHandleMsg( I18nUtil.getString("joblog_lost_fail") );

        XxlJobCompleter.updateHandleInfoAndFinish(jobLog);
    }

}

二、执行器xxl-job-core

1、XxlJob实例化

通过XxlJobSpringExecutor实例化注解@XxlJob的任务(代理) ,并初始化XxlJobExecutor

 for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
    Method executeMethod = methodXxlJobEntry.getKey();
    XxlJob xxlJob = methodXxlJobEntry.getValue();
    if (xxlJob == null) {
        continue;
    }

    String name = xxlJob.value();
    ...
    if (xxlJob.init().trim().length() > 0) {
        try {
            initMethod = bean.getClass().getDeclaredMethod(xxlJob.init());
            initMethod.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + bean.getClass() + "#" + executeMethod.getName() + "] .");
        }
    }
    if (xxlJob.destroy().trim().length() > 0) {
        try {
            destroyMethod = bean.getClass().getDeclaredMethod(xxlJob.destroy());
            destroyMethod.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + bean.getClass() + "#" + executeMethod.getName() + "] .");
        }
    }

    // registry jobhandler
    registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));
}

2、初始化XxlJobExecutor

    public void start() throws Exception {

        // init logpath
        XxlJobFileAppender.initLogPath(logPath);

        // init invoker, admin-client
        initAdminBizList(adminAddresses, accessToken);


        // init JobLogFileCleanThread  日志清理线程池
        JobLogFileCleanThread.getInstance().start(logRetentionDays);

        // init TriggerCallbackThread  日志完成结果回调线程池
        TriggerCallbackThread.getInstance().start();

        // init executor-server  RPC网关
        initEmbedServer(address, ip, port, appname, accessToken);
    }

JobLogFileCleanThread:日志清理线程池

3、日志完成结果回调

TriggerCallbackThread:日志完成结果回调线程池

// init logpath
XxlJobFileAppender.initLogPath(logPath);

// init invoker, admin-client
initAdminBizList(adminAddresses, accessToken);


// init JobLogFileCleanThread
JobLogFileCleanThread.getInstance().start(logRetentionDays);

// init TriggerCallbackThread
TriggerCallbackThread.getInstance().start();

// init executor-server 初始化xxl-rpc 
initEmbedServer(address, ip, port, appname, accessToken);

4、XXL-RPC及ExecutorRegistryThread实例化

try {
    // start server
    ServerBootstrap bootstrap = new ServerBootstrap();
  //netty nio运用
    bootstrap.group(bossGroup, workerGroup)
            .channel(NioServerSocketChannel.class)
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel channel) throws Exception {
                    channel.pipeline()
                            .addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS))  // beat 3N, close if idle
                            .addLast(new HttpServerCodec())
                            .addLast(new HttpObjectAggregator(5 * 1024 * 1024))  // merge request & reponse to FULL
                            .addLast(new EmbedHttpServerHandler(executorBiz, accessToken, bizThreadPool));
                }
            })
            .childOption(ChannelOption.SO_KEEPALIVE, true);

    // bind
    ChannelFuture future = bootstrap.bind(port).sync();

    logger.info(">>>>>>>>>>> xxl-job remoting server start success, nettype = {}, port = {}", EmbedServer.class, port);

    // start registry
    startRegistry(appname, address);

    // wait util stop
    future.channel().closeFuture().sync();

}

...

ExecutorRegistryThread.getInstance().start(appname, address);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值