当前位置: 首页 > news >正文

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 的调度请求,执行器会:

  1. 根据 jobId 找到对应的线程:
JobThread thread = jobThreadRepository.get(1001);
  1. 如果没有,则新建线程并注册:
JobThread newThread = new JobThread(1001, handler);
jobThreadRepository.put(1001, newThread);
  1. 然后将调度任务交给该线程处理。

2.4. jobThreadRepository与jobHandlerRepository的区别

名称

存的内容

类型

用途

类比

jobHandlerRepository

"任务名" => IJobHandler

任务逻辑

任务执行逻辑注册

类似“任务执行策略”字典

jobThreadRepository

jobId => JobThread

执行线程

任务执行载体

类似“线程池中线程索引”

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. ✅ 总结

问题

说明

jobHandlerRepository 是内存级别的吗?

✅ 是的,只存在于当前进程内存中

重启 XXL-JOB 执行器服务后会清空吗?

✅ 会清空

是否需要重新注册任务?

✅ 需要,但框架会自动完成

什么时候注册的?

Spring 容器启动完成后,XxlJobSpringExecutor.afterSingletonsInstantiated()中注册

需要我们手动操作吗?

❌ 不需要,只要配置了 @XxlJob注解

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)

主节点选举

通过数据库锁(如 LOCK_NAME='schedule_lock')互斥调度

服务访问

通过 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

  1. 三台机器上部署相同的 xxl-job-admin 应用(jar包或war包)
  2. 修改 application.properties,指向相同的 MySQL 数据库
  3. 使用 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 数据库─────┘┌────────────────────────────────────────────┐
│ 多台执行器定期注册 / 接收调度请求           │
└────────────────────────────────────────────┘

博文参考

http://www.xdnf.cn/news/948511.html

相关文章:

  • 什么是VR全景技术
  • 【JMeter】接口断言
  • 在WSL2的Ubuntu镜像中安装Docker
  • claude3.7高阶玩法,生成系统架构图,国内直接使用
  • CSS 工具对比:UnoCSS vs Tailwind CSS,谁是你的菜?
  • Linux信号保存与处理机制详解
  • 自然语言处理——循环神经网络
  • PKIX path building failed问题小结
  • Element-Plus:popconfirm与tooltip一起使用不生效?
  • Spring数据访问模块设计
  • Python自然语言处理库之gensim使用详解
  • Appuploader:在WindowsLinux上完成iOS APP上架的一种解决方案
  • RLHF vs RLVR:对齐学习中的两种强化方式详解
  • Rsync+inotify+nfs实现数据实时备份方案
  • Socket 编程
  • 架构设计之存储高性能——非关系型数据库(NoSQL)
  • 代购商城系统怎么选?从业务痛点看系统核心价值
  • SOC-ESP32S3部分:QA-关于唤醒词更改及配置操作步骤
  • 解锁Vscode:C/C++环境配置超详细指南
  • Python训练营---DAY49
  • 卷积神经网络设计指南:从理论到实践的经验总结
  • FDMA:解锁PL DDR性能的“高速快递系统”
  • Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
  • 论文笔记:Urban Computing in the Era of Large Language Models
  • 多模态大语言模型arxiv论文略读(113)
  • Vue3+ts项目,在ts文件中导入vue文件,报错:找不到模块“./App.vue“或响应的类型声明
  • Easy Rules规则引擎:轻量级Java规则处理实践指南
  • 微机原理与接口技术,期末冲刺复习资料(四)
  • Python_day49cbam模块介绍
  • 华为云Flexus+DeepSeek征文 | 基于Dify构建具备联网搜索能力的知识库问答助手