导读
xxl-job是一个分布式任务调度平台,在业内深受广大程序员的喜爱,本章将带你深入了解xxl-job的源码,理解其运行逻辑。
阅读xxl-job源码会增强你对多线程的理解与应用,调度思想的升华
本章将带你一点点剖析xxl-job设计底层逻辑,让你真正理解下图的每一个模块,让你知其然更知其所以然。
项目结构
拉下代码我们首先看项目结构,xxl-job为标准的父子工程,我们看到有三个子项目
- xxl-job-admin 服务端
- xxl-job-core 核心包,会被服务端和客户端同时引用
- xxl-job-executor-samples 一些demo
源码解读——定时器
ok,现在我们需要知道xxl-job服务端启动都干了什么,首先定位到这个类XxlJobAdminConfig
可以看到xxl-job核心功能是跟随bean的生命周期运行的
@Component
public class XxlJobAdminConfig implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
adminConfig = this;
xxlJobScheduler = new XxlJobScheduler();
// 初始化
xxlJobScheduler.init();
}
@Override
public void destroy() throws Exception {
xxlJobScheduler.destroy();
}
}
跟一下代码com.xxl.job.admin.core.scheduler.XxlJobScheduler#init
可以看到,此处初始化了几个核心的helper,每个helper都运用了饿汉单例模式,不会出现重复创建的情况
public void init() throws Exception {
// 触发池
JobTriggerPoolHelper.toStart();
// 服务监听器
JobRegistryHelper.getInstance().start();
// 失败告警
JobFailMonitorHelper.getInstance().start();
// 回调监听器
JobCompleteHelper.getInstance().start();
// 日志回调
JobLogReportHelper.getInstance().start();
// 定时器
JobScheduleHelper.getInstance().start();
}
首先定位到com.xxl.job.admin.core.thread.JobScheduleHelper#start
该helper就是定时器的心脏
,其作用为定时和预触发。
启动了两个守护线程
-
scheduleThread 任务扫描线程
while循环,不停的扫描即将执行的任务,使用
mysql for update实现排他锁
,防止其他服务并行扫描,并将即将执行的任务缓存到时间轮
中 -
ringThread 执行线程
触发
时间轮
中的任务
时间轮
来看看时间轮这个听起来高大上的东西是如何实现的
如下,本质就是一个concurrentHashMap
,key为执行的秒,value为要执行的job的id,scheduleThread
线程会提前5-10秒将任务放入时间轮的list中。
private volatile static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
// ringSecond 下一次执行的时间
private void pushTimeRing(int ringSecond, int jobId){
// push async ring
List<Integer> ringItemData = ringData.get(ringSecond);
if (ringItemData == null) {
ringItemData = new ArrayList<Integer>();
ringData.put(ringSecond, ringItemData);
}
ringItemData.add(jobId);
}
触发时间轮中的任务
ringThread = new Thread(new Runnable() {
@Override
public void run() {
while (!ringThreadToStop) {
try {
// 时间对其
TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
} catch (InterruptedException e) {
if (!ringThreadToStop) {
logger.error(e.getMessage(), e);
}
}
try {
// 时间轮数据处理
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);
}
}
if (ringItemData.size() > 0) {
// 触发任务
for (int jobId: ringItemData) {
// 触发任务
JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1,