通过上一节 业务方接入启动流程可以看到,业务方会启动一些线程,以一定的频率调用 /api/callback,/api/registry,/api/registryRemove,本章就讲解admin启动的流程和怎么调用定时任务
一.admin启动的核心
admin就是一个普通的springboot项目,启动后有如下几个核心类
1.1.JobApiController
1.1.1 callback方法
方法很简单,就是从xxl_job_log表中找到这个日志,同步执行的结果
1.1.2 registry方法
将执行器的参数,添加到xxl_job_registry表中
1.1.3 registryRemove方法
将执行器,从表xxl_job_registry中删除
1.2 JobInfoController类
备注:该类主要是对任务的增删改查操作 核心方法
1.2.1 添加任务/jobinfo/add
这个方法就是保存页面的任务参数到xxl-job-info表
1.2.2 启动/暂停任务 /jobinfo/start|stop
概要: 这一块都比较简单,都是一些常见的crud
1.3 初始化启动线程,执行相应的任务
XxlJobAdminConfig类的
public void afterPropertiesSet() throws Exception {
adminConfig = this;
xxlJobScheduler = new XxlJobScheduler();
xxlJobScheduler.init();
}
public void init() throws Exception {
// 1.多语言初始化
initI18n();
//初始化fastTriggerPool和slowTriggerPool线程池对象
JobTriggerPoolHelper.toStart();
/**
* 开启线程,每90s查询执行器的数据,如果执行器上次更新时间超过90s未更新,就移除这个执行器,并把存活的执行器更新
*/
JobRegistryHelper.getInstance().start();
/**
* 启动线程,查找任务执行失败的任务,
* 1.设置了重试次数,就再次触发任务
* 2.判断是否需邮件预警
*/
JobFailMonitorHelper.getInstance().start();
/**
* 启动线程,处理任务结果丢失的数据
* 任务结果丢失处理:调度记录停留在 "运行中" 状态超过10min,且对应执行器心跳注册失败不在线,则将本地调度主动标记失败;
*/
JobCompleteHelper.getInstance().start();
JobLogReportHelper.getInstance().start();
/**
* todo 执行任务(核心)
* 启动线程,执行任务
*/
JobScheduleHelper.getInstance().start();
logger.info(">>>>>>>>> init xxl-job admin success.");
}
这些核心步骤,会在第三章中讲解
二.任务定时触发
在 ** 1.3 初始化启动线程,执行相应的任务**中,看到会启动很多线程,执行不同的事情,最重要的是最后一步
JobScheduleHelper.getInstance().start();
接下来我们就来进行分析
public void start(){
// 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.");
// 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
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、TODO 查询从当前时间+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 todo 如果任务超时5秒
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 todo 如果任务配置的"调度过期策略"是"立即执行一次",那么就触发一次任务
JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.MISFIRE, -1, null, null, null);
logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );
}
// 2、fresh next todo 从当前时间开始,计算任务的下一次执行时间
refreshNextValidTime(jobInfo, new Date());
} else if (nowTime > jobInfo.getTriggerNextTime()) {
// 2.2、trigger-expire < 5s:direct-trigger && make next-trigger-time todo 任务执行时间在当前时间的5s内
// 1、trigger TODO 触发任务
JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );
// 2、fresh next todo 从当前时间开始,计算任务的下一次执行时间
refreshNextValidTime(jobInfo, new Date());
// next-trigger-time in 5s, pre-read again todo 如果下一次的执行时间在未来5s内
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 {
//TODO 还没有到达任务执行的时间
// 2.3、trigger-pre-read:time-ring trigger && make next-trigger-time
// 1、TODO 计算剩余的秒数字
int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
// 2、TODO 把剩余秒数--任务id存入map中 ;==>下面的 ringThread 线程,会每一秒执行一次,查到对应的数据后,触发任务
pushTimeRing(ringSecond, jobInfo.getId());
// 3、TODO重新计算下一次时间
refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
}
}
// 3、update trigger info TODO 修改jonInfo的内容
for (XxlJobInfo jobInfo: scheduleList) {
XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
}
} else {
preReadSuc = false;
}
这一段代码比较长,注释已经添加到代码中了,核心就是做了几件事
- 查询任务触发时间 < 当前时间+5秒的任务
- 如果任务超时5秒没有执行,但是如果任务配置的"调度过期策略"是"立即执行一次",那么就触发一次任务
- 如果任务+5秒<当前时间,就进行触发,并计算下一次执行的时间
- 如果任务还没到时间,就不触发
接着,我们进入触发任务代码
JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
接着进入
public static void trigger(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam, String executorParam, String addressList) {
helper.addTrigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);
}
接着会放入线程池中执行
com.xxl.job.admin.core.thread.JobTriggerPoolHelper#addTrigger
XxlJobTrigger.trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);
最终会调用 com.xxl.job.admin.core.trigger.XxlJobTrigger#processTrigger
这个方法核心的步骤
executorRouteStrategyEnum.getRouter().route(triggerParam, group.getRegistryList());
常用的路由策略逻辑如下
- 第一个
- 最后一个
同理选addressList的最后一个元素
- 轮询
-故障转移
从上面常用的路由策略,可以看到逻辑都比较简单
路由策略选好地址后,接下来就是调用
runExecutor(triggerParam, address);
@Override
public ReturnT<String> run(TriggerParam triggerParam) {
//发送http请求,调用执行器的run方法
return XxlJobRemotingUtil.postBody(addressUrl + "run", accessToken, timeout, triggerParam, String.class);
}