xxl-job-admin(Bean模式)启动、执行、停止任务简单分析

1、点击启动任务按钮会请求/jobInfo/start,执行XxlJobService#start()方法

@RequestMapping("/start")		// TODO, resume >> start
	@ResponseBody
	public ReturnT start(int id) {
		return xxlJobService.start(id);
	}

2、根据jobId查询XxlJobInfo信息,并添加到XxlJobDynamicSchheduler.addJob()

@Override
	public ReturnT start(int id) {
		XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
		String group = String.valueOf(xxlJobInfo.getJobGroup());
		String name = String.valueOf(xxlJobInfo.getId());
		String cronExpression = xxlJobInfo.getJobCron();

		try {
			boolean ret = XxlJobDynamicScheduler.addJob(name, group, cronExpression);
			return ret?ReturnT.SUCCESS:ReturnT.FAIL;
		} catch (SchedulerException e) {
			logger.error(e.getMessage(), e);
			return ReturnT.FAIL;
		}
	}

3、 把任务添加到quartz中,并由quartz根据cron表达式触发RemoteHttpJobBean#executeInternal(),再由任务id去执行

public static boolean addJob(String jobName, String jobGroup, String cronExpression) throws SchedulerException {
    	// 这里的jobName就是任务id
        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
        JobKey jobKey = new JobKey(jobName, jobGroup);

        // 2、valid
        if (scheduler.checkExists(triggerKey)) {
            return true;    // PASS
        }

        // 3、corn trigger
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();   // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();

        // 4、由quartz触发的类
		Class jobClass_ = RemoteHttpJobBean.class;   // Class.forName(jobInfo.getJobClass());
		JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
            
        // 5、开始调度
        Date date = scheduler.scheduleJob(jobDetail, cronTrigger);

        logger.info(">>>>>>>>>>> addJob success, jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
        return true;
    }

4、触发JobTriggerPoolHelper.trigger()进行任务的调度

/**
     *这里会用一个自定义的线程池去执行XxlJobTrigger#trigger()
     * 当启动的线程数量少于32的时候,会一直创建线程去处理,
     * 当线程数量大于32个的时候,后续的线程会放入有界的队列中,并等待60秒
     * 当放入队列中的线程数量大于1000个的时候,会继续创建线程
     * 直到线程数达到256个
     */
    private ThreadPoolExecutor triggerPool = new ThreadPoolExecutor(
            32,
            256,
            60L,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue(1000));

    
    public void addTrigger(final int jobId, final TriggerTypeEnum triggerType, final int failRetryCount, final String executorShardingParam, final String executorParam) {
        triggerPool.execute(new Runnable() {
            @Override
            public void run() {
                XxlJobTrigger.trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam);
            }
        });
    }

5、load相关信息并执行processTrigger()

public static void trigger(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam, String executorParam) {
        // 根据jobId获取对应的jobInfo
        XxlJobInfo jobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(jobId);
        if (jobInfo == null) {
            logger.warn(">>>>>>>>>>>> trigger fail, jobId invalid,jobId={}", jobId);
            return;
        }
        //设置任务时候的执行参数
        if (executorParam != null) {
            jobInfo.setExecutorParam(executorParam);
        }
        //设置失败重试次数
        int finalFailRetryCount = failRetryCount>=0?failRetryCount:jobInfo.getExecutorFailRetryCount();
        //获取执行器信息
        XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(jobInfo.getJobGroup());

        // 调度请求的executorShardingParam默认是null。所以跳过这一步
        int[] shardingParam = null;
        if (executorShardingParam!=null){
            String[] shardingArr = executorShardingParam.split("/");
            if (shardingArr.length==2 && StringUtils.isNumeric(shardingArr[0]) && StringUtils.isNumeric(shardingArr[1])) {
                shardingParam = new int[2];
                shardingParam[0] = Integer.valueOf(shardingArr[0]);
                shardingParam[1] = Integer.valueOf(shardingArr[1]);
            }
        }
        //判断路由模式,是否是分片
        if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null)
                && CollectionUtils.isNotEmpty(group.getRegistryList()) && shardingParam==null) {
            //是的话会分多次执行,调用所有的执行器机器
            for (int i = 0; i < group.getRegistryList().size(); i++) {
                //执行触发请求
                processTrigger(group, jobInfo, finalFailRetryCount, triggerType, i, group.getRegistryList().size());
            }
        } else {
            //非分片模式全部只会走一次,确保只执行一台机器
            if (shardingParam == null) {
                shardingParam = new int[]{0, 1};
            }
            processTrigger(group, jobInfo, finalFailRetryCount, triggerType, shardingParam[0], shardingParam[1]);
        }

    }

6、processTrigger()设置相关的Trigger参数,进行rpc请求的调用,保存调度日志等

private static void processTrigger(XxlJobGroup group, XxlJobInfo jobInfo, int finalFailRetryCount, TriggerTypeEnum triggerType, int index, int total){

        // 获取阻塞枚举,默认是单机执行
        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准备rpc请求
        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.setLogDateTim(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 routeAddressResult = null;
        if (CollectionUtils.isNotEmpty(group.getRegistryList())) {
            //循环调用分片模式下,注册的机器,,并给本次触发的机器地址赋值
            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(ReturnT.FAIL_CODE, I18nUtil.getString("jobconf_trigger_address_empty"));
        }
        // 4、trigger remote executor
        ReturnT triggerResult = null;
        if (address != null) {
            //执行rpc请求
            triggerResult = runExecutor(triggerParam, address);
        } else {
            triggerResult = new ReturnT(ReturnT.FAIL_CODE, null);
        }

     //...忽略
    }

7、执行runExecutor进行rpc触发执行器调用

public static ReturnT runExecutor(TriggerParam triggerParam, String address){
        ReturnT runResult = null;
        try {
            //拿到构建了动态代理的ExecutorBiz->XxlRpcReferenceBean
            ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
            //进入动态代理,进行rpc请求,调用执行器的ExecutorBizImpl#run方法
            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(ReturnT.FAIL_CODE, ThrowableUtil.toString(e));
        }

        StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + ":");
        runResultSB.append("
address:").append(address); runResultSB.append("
code:").append(runResult.getCode()); runResultSB.append("
msg:").append(runResult.getMsg()); runResult.setMsg(runResultSB.toString()); return runResult; }

8、至此,启动过程分析结束,执行过程只是一次性触发JobTriggerPoolHelper.trigger,这里不再多说

================================================================================================

接下来说一下更新和停止

1、更新实际上就是通过触发quartz调度器rescheduleJob方法去修改quartz调度逻辑,执行流程与启动一致,感兴趣的可以自己看

//...省略大部分代码
try {
            XxlJobDynamicScheduler.updateJobCron(qz_group, qz_name, exists_jobInfo.getJobCron());
        } catch (SchedulerException e) {
            logger.error(e.getMessage(), e);
			return ReturnT.FAIL;
        }

 // 5、rescheduleJob
        scheduler.rescheduleJob(triggerKey, oldTrigger);

2、停止,通过quartz调度器的unscheduleJob()方法,取消quartz的调度

public ReturnT stop(int id) {
        XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
        String group = String.valueOf(xxlJobInfo.getJobGroup());
        String name = String.valueOf(xxlJobInfo.getId());

		try {
			// bind quartz
            boolean ret = XxlJobDynamicScheduler.removeJob(name, group);
            return ret?ReturnT.SUCCESS:ReturnT.FAIL;
		} catch (SchedulerException e) {
			logger.error(e.getMessage(), e);
			return ReturnT.FAIL;
		}
	}

public static boolean removeJob(String jobName, String jobGroup) throws SchedulerException {

        TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);

        if (scheduler.checkExists(triggerKey)) {
            scheduler.unscheduleJob(triggerKey);    // trigger + job
        }

        logger.info(">>>>>>>>>>> removeJob success, triggerKey:{}", triggerKey);
        return true;
    }

================================================================================================

到了这里,启动、执行、更新、停止的过程我们都说完了。那么任务失败的报警呢?这里就需要回到启动的地方了

1、xxl-job-admin会在启动的时候,启动一个JobFail的守护线程monitorThread

public void start() throws Exception {
        // valid
        Assert.notNull(scheduler, "quartz scheduler is null");

        // init i18n
        initI18n();

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

        // 出错任务的处理线程
        JobFailMonitorHelper.getInstance().start();

        // admin-server
        initRpcProvider();

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

2、然后会每10秒从表中查出报错的列表,并触发failAlarm(),默认只有email报错处理,我们可以在failAlarm()方法这里,自行扩展其他报警策略

public void start(){
		monitorThread = new Thread(new Runnable() {

			@Override
			public void run() {

				// monitor
				while (!toStop) {
					try {
                        //查询1000条报错日志
						List failLogIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findFailJobLogIds(1000);
						if (CollectionUtils.isNotEmpty(failLogIds)) {
                           //遍历处理
							for (int failLogId: failLogIds) {

								// lock log
								int lockRet = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, 0, -1);
								if (lockRet < 1) {
									continue;
								}
								XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(failLogId);
								XxlJobInfo info = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(log.getJobId());

								// 1、fail retry monitor
								if (log.getExecutorFailRetryCount() > 0) {
									JobTriggerPoolHelper.trigger(log.getJobId(), TriggerTypeEnum.RETRY, (log.getExecutorFailRetryCount()-1), log.getExecutorShardingParam(), null);
									String retryMsg = "

>>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_type_retry") +"<<<<<<<<<<<
"; log.setTriggerMsg(log.getTriggerMsg() + retryMsg); XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(log); } // 2、fail alarm monitor int newAlarmStatus = 0; // 告警状态:0-默认、-1=锁定状态、1-无需告警、2-告警成功、3-告警失败 if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) { boolean alarmResult = true; try { //失败报警 alarmResult = failAlarm(info, log); } catch (Exception e) { alarmResult = false; logger.error(e.getMessage(), e); } newAlarmStatus = alarmResult?2:3; } else { newAlarmStatus = 1; } XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, -1, newAlarmStatus); } } //每10秒执行一次 TimeUnit.SECONDS.sleep(10); } catch (Exception e) { logger.error("job monitor error:{}", e); } } } }); monitorThread.setDaemon(true); monitorThread.start(); }

3、报警方法

private boolean failAlarm(XxlJobInfo info, XxlJobLog jobLog){
		boolean alarmResult = true;

		// send monitor email
		if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) {

			String alarmContent = "Alarm Job LogId=" + jobLog.getId();
			if (jobLog.getTriggerCode() != ReturnT.SUCCESS_CODE) {
				alarmContent += "
TriggerMsg=
" + jobLog.getTriggerMsg(); } if (jobLog.getHandleCode()>0 && jobLog.getHandleCode() != ReturnT.SUCCESS_CODE) { alarmContent += "
HandleCode=" + jobLog.getHandleMsg(); } Set emailSet = new HashSet(Arrays.asList(info.getAlarmEmail().split(","))); for (String email: emailSet) { XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(Integer.valueOf(info.getJobGroup())); String personal = I18nUtil.getString("admin_name_full"); String title = I18nUtil.getString("jobconf_monitor"); String content = MessageFormat.format(mailBodyTemplate, group!=null?group.getTitle():"null", info.getId(), info.getJobDesc(), alarmContent); // make mail try { MimeMessage mimeMessage = XxlJobAdminConfig.getAdminConfig().getMailSender().createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); helper.setFrom(XxlJobAdminConfig.getAdminConfig().getEmailUserName(), personal); helper.setTo(email); helper.setSubject(title); helper.setText(content, true); XxlJobAdminConfig.getAdminConfig().getMailSender().send(mimeMessage); } catch (Exception e) { logger.error(">>>>>>>>>>> job monitor alarm email send error, JobLogId:{}", jobLog.getId(), e); alarmResult = false; } } } // TODO, custom alarm strategy, such as sms //作者也提示了,需要扩展其他报警策略,可以在这里扩展 return alarmResult; }

================================================================================================分析结束,谢谢

你可能感兴趣的:(xxl-job)