xxljob调度中心架构解读
本文基于2.1.2版本。
基础数据结构
- xxlJobInfo(com.xxl.job.admin.core.model.XxlJobInfo)记录任务的所有信息。任务名称,任务cron表达式,任务所属执行器,任务下次执行时间等。
- xxlJobGroup(com.xxl.job.admin.core.model.XxlJobGroup)记录执行器信息。与调度相关的字段是机器列表,记录了所有存活的机器。
- xxlJobLog(com.xxl.job.admin.core.model.XxlJobLog) 记录任务调度日志。
- xxlJobRegistry(com.xxl.job.admin.core.model.XxlJobRegistry)执行机器注册表,记录所有连接到调度中心的客户端。
客户端心跳检测
实现类:com.xxl.job.admin.core.thread.JobRegistryMonitorHelper。该部分负责将xxlJobRegistry注册表中的存活机器(心跳时间没有超过死亡时间的机器)刷新到xxlJobGroup的registryList字段中,提供给其他模块使用。通过该模块,可以大部分情况下确保xxlJobGroup中的registryList全部是存活机器。
注册表里的信息是客户端不断的去注册中心触发心跳,通知注册中心存活。
任务调度
实现类:com.xxl.job.admin.core.thread.JobScheduleHelper。该部分由两个线程配合完成,一个是预读调度线程scheduleThread,一个是触发调度线程ringThread。
ringData
ringData:是一个map结构,key存储的是秒(0-59),value存储的是需要触发的任务list。
private volatile static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
scheduleThread
主要负责预读后五秒的调度任务,将任务插入到ringData中。
针对读取到的任务,有三个处理策略:
- 已经超过触发时间五秒的任务,直接丢弃。
- 已经超过触发时间但未超过五秒的任务,直接触发。并且计算下次触发时间,如果在预读范围,插入到ringData中。
- 未到触发时间的任务,插入到ringData中。该分支也是主要执行分支,上两个分支只有在特殊情况下才会出现。
// schedule thread
scheduleThread = new Thread(new Runnable() {
@Override
public void run() {
try {
//睡眠4-5s 这个睡眠的意义是什么????
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