xxljob调度中心启动源码解析
源码版本:
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job</artifactId>
<version>2.4.0-SNAPSHOT</version>
</dependency>
入口类为:com.xxl.job.admin.core.conf.XxlJobAdminConfig。因为该类实现了InitializingBean接口,因此在spring启动初始化bean时会回调该类的afterPropertiesSet方法。
@Component
public class XxlJobAdminConfig implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
adminConfig = this;
xxlJobScheduler = new XxlJobScheduler();
// 进行xxljob调度中心的初始化
xxlJobScheduler.init();
}
}
接着来到com.xxl.job.admin.core.scheduler.XxlJobScheduler的init方法:
public void init() throws Exception {
// init i18n
// 初始化新增任务页面中的阻塞处理策略相关的国际化信息。国际化提供中文、英文两种可选语言,默认为中文
initI18n();
// admin trigger pool start
// 初始化一个快处理线程池和一个慢处理线程池。如果一个任务在1分钟之内出现10次调用超时,则这个任务会加入到慢处理线程池中进行调度
// 使执行任务的线程池隔离:调度线程池进行隔离拆分,慢任务自动降级进入"Slow"线程池,避免耗尽调度线程,提高系统稳定性。
JobTriggerPoolHelper.toStart();
// admin registry monitor run
// 开启一个守护线程主要管理执行器的注册和注销(即任务的调用地址管理)
JobRegistryHelper.getInstance().start();
// admin fail-monitor run
// 开启一个守护线程主要管理异常调用的任务。如果任务调用失败,它会判断这个任务是否配置了失败重试(默认不重试)。
// 如果配置重试次数大于0就会执行失败重试,如果一直失败最大重试次数就是你配置的重试次数
JobFailMonitorHelper.getInstance().start();
// admin lose-monitor run ( depend on JobTriggerPoolHelper )
// 初始化一个回调线程池、以及启动一个监听线程(对于任务结果丢失处理:调度记录停留在 "运行中" 状态超过10min,且对应执行器心跳注册失败不在线,则将本地调度主动标记失败)
JobCompleteHelper.getInstance().start();
// admin log report start
// 报表相关的功能,保存任务执行的日志。并且生成日结报表,并且清理过期日志。
// 支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等
JobLogReportHelper.getInstance().start();
// start-schedule ( depend on JobTriggerPoolHelper )
// 这里会创建两个线程:一个线程是从xxl_job_info表中取出未来5秒内要执行的任务,然后将任务按照执行时间加入到ringData(时间轮)中
// 另一个线程则是从ringData(时间轮)中取出数据进行任务调度
JobScheduleHelper.getInstance().start();
logger.info(">>>>>>>>> init xxl-job admin success.");
}
接下来主要看com.xxl.job.admin.core.thread.JobScheduleHelper的start方法中ringThread线程的处理逻辑
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