XXL-JOB——源码分析解读(2)
摘要
本文深入分析了XXL-JOB任务调度框架的核心源码,重点探讨了任务处理器注册表(jobHandlerRepository)和任务执行线程注册表(jobThreadRepository)的实现与作用。jobHandlerRepository用于存储任务名称与任务处理器的映射关系,便于任务调度时快速找到对应的执行逻辑。jobThreadRepository则负责管理任务执行线程,支持任务的注册、移除和获取操作。文章还讨论了jobHandlerRepository的内存级别特性,以及XXL-JOB执行器重启后任务处理器需重新注册的原因。此外,涉及了通过HTTP接口注册任务与执行器注册JobHandler的区别,并探讨了XXL-JOB调度中心的集群部署设计,包括集群部署原理、传统多机部署、Docker Compose部署方式以及执行器连接多个Admin的配置方法。
1. 任务处理器注册表源码
// ---------------------- job handler repository ----------------------
private static ConcurrentMap<String, IJobHandler> jobHandlerRepository = new ConcurrentHashMap<String, IJobHandler>();// 注册JobHandler
public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){logger.info(">>>>>>>>>>> xxl-job register jobhandler success, name:{}, jobHandler:{}", name, jobHandler);return jobHandlerRepository.put(name, jobHandler);
}
// 加载JobHandler
public static IJobHandler loadJobHandler(String name){return jobHandlerRepository.get(name);
}
1.1. jobHandlerRepository作用:
存储 Job 名称(String)与任务处理器(IJobHandler 实例)之间的映射关系。
1.2. ✅ 用于:
- 在任务调度时,根据任务名找到对应的
IJobHandler
执行逻辑。 IJobHandler
是任务的核心执行逻辑对象。
1.3. ✅ 举例:
你定义了一个任务:
@XxlJob("userDataSync")
public ReturnT<String> sync(String param) {
// 任务逻辑
}
框架会调用:
registJobHandler("userDataSync", new MethodJobHandler(...));
此时 jobHandlerRepository
中:
"userDataSync" => MethodJobHandler 实例(封装了你的方法)
2. 任务执行线程注册表源码
// ---------------------- job thread repository ----------------------// job线程仓库
private static ConcurrentMap<Integer, JobThread> jobThreadRepository = new ConcurrentHashMap<Integer, JobThread>();// 注册JobThread
public static JobThread registJobThread(int jobId, IJobHandler handler, String removeOldReason){JobThread newJobThread = new JobThread(jobId, handler);newJobThread.start();logger.info(">>>>>>>>>>> xxl-job regist JobThread success, jobId:{}, handler:{}", new Object[]{jobId, handler});JobThread oldJobThread = jobThreadRepository.put(jobId, newJobThread);// putIfAbsent | oh my god, map's put method return the old value!!!if (oldJobThread != null) {oldJobThread.toStop(removeOldReason);oldJobThread.interrupt();}return newJobThread;
}// 移除JobThread
public static JobThread removeJobThread(int jobId, String removeOldReason){JobThread oldJobThread = jobThreadRepository.remove(jobId);if (oldJobThread != null) {oldJobThread.toStop(removeOldReason);oldJobThread.interrupt();return oldJobThread;}return null;
}// 获取JobThread
public static JobThread loadJobThread(int jobId){
JobThread jobThread = jobThreadRepository.get(jobId);
return jobThread;
}
2.1. jobThreadRepository作用:
存储 JobId 与 JobThread(线程)之间的映射关系,用于实际执行任务。
2.2. ✅ 用于:
- 为每一个任务调度创建一个
JobThread
(执行线程)。 - 每次任务被调度执行时,都会找到对应的
JobThread
来运行任务。 - 每个
JobThread
内部会运行一个Runnable
循环,从任务队列取任务执行。
2.3. ✅ 举例:
调度中心发送了 jobId = 1001 的调度请求,执行器会:
- 根据 jobId 找到对应的线程:
JobThread thread = jobThreadRepository.get(1001);
- 如果没有,则新建线程并注册:
JobThread newThread = new JobThread(1001, handler);
jobThreadRepository.put(1001, newThread);
- 然后将调度任务交给该线程处理。
2.4. jobThreadRepository与jobHandlerRepository的区别
名称 | 存的内容 | 类型 | 用途 | 类比 |
|
| 任务逻辑 | 任务执行逻辑注册 | 类似“任务执行策略”字典 |
|
| 执行线程 | 任务执行载体 | 类似“线程池中线程索引” |
3. jobHandlerRepository 是内存的级别的? 如果xxl-job 服务重启后,注册任务是否需要重新注册?
是的,jobHandlerRepository
是纯内存级别的注册表,它的生命周期和 XXL-JOB 执行器服务(也就是你的 Spring Boot 应用)是一致的。但你不用手动注册,XXL-JOB 框架会自动帮你重新注册。
下面是详细解释:
3.1. ✅ jobHandlerRepository
是内存级别的
private static ConcurrentMap<String, IJobHandler> jobHandlerRepository = new ConcurrentHashMap<>();
- 它是一个
static
的静态成员变量。 - 数据存储在 JVM 内存中。
- 一旦 JVM 重启、服务关闭,它就会被清空。
3.2. ❗ XXL-JOB 执行器重启后,JobHandler需要重新注册吗?
3.2.1. ✅ 是的,需要重新注册!
- 因为注册表是内存级别的,重启后会清空。
- 但你不用手动注册,XXL-JOB 框架会自动帮你重新注册。
3.3. ✅ 为什么不需要手动注册?
这是因为 XXL-JOB 执行器在启动时会自动扫描 Spring 容器中的 @XxlJob
注解方法:
XxlJobSpringExecutor implements SmartInitializingSingleton
在 Spring 容器初始化完成后,它的 afterSingletonsInstantiated()
方法会被回调:
@Override
public void afterSingletonsInstantiated() {initJobHandlerMethodRepository(applicationContext); // 自动扫描 @XxlJob 注解并注册super.start();
}
- 这个方法中会调用:
registJobHandler("yourJobName", new MethodJobHandler(...));
- 所以,只要你的任务方法上写了
@XxlJob("yourJobName")
注解,每次服务启动都会自动重新注册。
3.4. ✅ 总结
问题 | 说明 |
| ✅ 是的,只存在于当前进程内存中 |
重启 XXL-JOB 执行器服务后会清空吗? | ✅ 会清空 |
是否需要重新注册任务? | ✅ 需要,但框架会自动完成 |
什么时候注册的? | Spring 容器启动完成后, |
需要我们手动操作吗? | ❌ 不需要,只要配置了 |
4. 如果通过http接口注册xxl-job任务也不需要重新注册吗?
4.1. 🔹 HTTP 接口注册任务(向调度中心注册“任务元数据”)
这是你通过调用 XXL-JOB 调度中心的 HTTP 接口,如 /jobinfo/add
来注册定时任务,这个注册是持久化的,存入数据库 xxl_job_info
表。
4.1.1. ✅ 特点:
- 是调度中心层面的注册。
- 存储在数据库里(通常是 MySQL)。
- 服务重启不会丢失。
- 一般由你在业务部署平台、初始化脚本中注册。
📌 所以:如果你通过 HTTP 接口向调度中心注册的任务是“定时任务信息”(如任务名、cron 表达式、负责人等),那么重启不需要重新注册。
4.2. 🔹 执行器注册 JobHandler(运行时任务处理器)
这是 @XxlJob
注解的方法,或通过 registJobHandler()
注册的 IJobHandler
对象 —— 属于执行器内部注册,存储在内存中。
4.2.1. ⚠️ 特点:
- 是执行器层面的注册。
- 保存在执行器 JVM 内存(
jobHandlerRepository
)。 - 服务重启就会清空,需要重新注册。
- 但:
@XxlJob
注解会在 Spring 启动后自动注册。
📌 所以:如果你通过 HTTP 注册了任务,但执行器中没有注册对应的 JobHandler
,调度时会报错:job handler not found
.
4.3. 案例示例
你注册了一个任务,cron 表达式是 "0 */5 * * * ?"
,任务名是 "demoJob"
,指向执行器 my-app-executor
。
- 如果你服务中写了:
@XxlJob("demoJob")
public ReturnT<String> execute(String param) {
System.out.println("执行任务: " + param);
return ReturnT.SUCCESS;
}
那么服务重启后,@XxlJob
会自动注册回来,任务可以继续跑。
- 如果你没有
@XxlJob("demoJob")
,也没有registJobHandler("demoJob", ...)
,那服务虽然还在,但调度中心发调度请求过去会失败:找不到 handler。
5. XXL-JOB 调度中心(Admin)的集群部署设计?
XXL-JOB 调度中心(Admin)的集群部署其实是非常简单的,它采用 “伪集群”架构,也就是多个 admin 实例部署 + 共享同一数据库 + 数据库分布式锁实现主节点选举和调度控制。
5.1. ✅ XXL-JOB Admin 集群部署原理
功能点 | 实现方式 |
配置同步 | 多节点共用 一个数据库(xxl_job) |
主节点选举 | 通过数据库锁(如 |
服务访问 | 通过 nginx、SLB、VIP 等统一入口访问任意一个 Admin 实例 |
健康检测 | nginx / k8s 会剔除宕机节点 |
5.2. ✅ 传统多机部署(Spring Boot)
假设你有三台机器:
IP | 用途 |
192.168.1.101 | Admin 实例1 |
192.168.1.102 | Admin 实例2 |
192.168.1.103 | Admin 实例3 |
- 三台机器上部署相同的
xxl-job-admin
应用(jar包或war包) - 修改
application.properties
,指向相同的 MySQL 数据库 - 使用 nginx 或 DNS 做负载均衡,统一访问入口
# 配置相同数据库
spring.datasource.url=jdbc:mysql://192.168.1.200:3306/xxl_job?useSSL=false
spring.datasource.username=root
spring.datasource.password=123456# 启动端口每台不同
server.port=8080(或8081/8082)
5.3. ✅ Docker Compose 方式(推荐)
可以快速搭建多节点环境,核心配置如下:
version: "3"
services:admin1:image: xuxueli/xxl-job-admin:2.4.0container_name: xxl-job-admin-1environment:PARAMS: "--spring.datasource.url=jdbc:mysql://mysql/xxl_job --spring.datasource.username=root --spring.datasource.password=123456"ports:- "8081:8080"admin2:image: xuxueli/xxl-job-admin:2.4.0container_name: xxl-job-admin-2environment:PARAMS: "--spring.datasource.url=jdbc:mysql://mysql/xxl_job --spring.datasource.username=root --spring.datasource.password=123456"ports:- "8082:8080"
5.4. ✅ 统一入口访问方案
- nginx 反向代理:
upstream xxl_admin {server 192.168.1.101:8080;server 192.168.1.102:8080;server 192.168.1.103:8080;
}
server {listen 80;server_name xxl-job-admin.mycorp.local;location / {proxy_pass http://xxl_admin;}
}
5.5. ✅ 执行器连接多个 Admin 怎么配置?
在执行器配置中加入多个地址:
xxl:job:admin:addresses: http://192.168.1.101:8080,http://192.168.1.102:8080,http://192.168.1.103:8080
💡 执行器会自动轮询连接其中可用的 admin,不影响注册和运行。
5.6. ✅ 总结部署图
┌──────────────┐│ nginx/slb │└─────┬────────┘│┌──────────────┼──────────────┐│ │ │
┌──────▼─────┐ ┌─────▼──────┐ ┌────▼───────┐
│ admin-1 │ │ admin-2 │ │ admin-3 │
└────┬───────┘ └────┬───────┘ └────┬───────┘│ │ │└─────────共用 MySQL 数据库─────┘┌────────────────────────────────────────────┐
│ 多台执行器定期注册 / 接收调度请求 │
└────────────────────────────────────────────┘