XXL-JOB 分布式任务调度平台深度学习指南

XXL-JOB 分布式任务调度平台深度学习指南

版本 : v1.1 | 日期 : 2026-06-03 | 作者 : 江华森

更新 : 2026-06-03 23:21 --- 新增真实云环境部署验证 + 6个踩坑记录

适用版本 : XXL-JOB 2.4.x ~ 2.5.0 | JDK : 8 / 17 | Spring Boot: 2.x / 3.x


目录

  • 一、基础认知篇
    • [1.1 什么是 XXL-JOB](#1.1 什么是 XXL-JOB)
    • [1.2 为什么需要分布式任务调度](#1.2 为什么需要分布式任务调度)
    • [1.3 核心架构组件](#1.3 核心架构组件)
  • 二、环境准备篇
    • [2.1 前期准备](#2.1 前期准备)
    • [2.2 源码下载与项目结构](#2.2 源码下载与项目结构)
  • 三、上机实战篇
    • [3.1 调度中心搭建](#3.1 调度中心搭建)
    • [3.2 执行器开发实战](#3.2 执行器开发实战)
    • [3.3 任务开发实战](#3.3 任务开发实战)
    • [3.4 Web 界面操作实战](#3.4 Web 界面操作实战)
    • [3.5 进阶功能实战](#3.5 进阶功能实战)
    • [3.6 集群部署实战](#3.6 集群部署实战)
    • [3.7 真实云环境部署验证](#3.7 真实云环境部署验证) 🆕
  • 四、高级应用篇
    • [4.1 性能优化](#4.1 性能优化)
    • [4.2 安全配置](#4.2 安全配置)
    • [4.3 监控告警](#4.3 监控告警)
  • 五、项目实战案例
    • [5.1 数据同步任务](#5.1 数据同步任务)
    • [5.2 报表生成系统](#5.2 报表生成系统)
    • [5.3 缓存预热任务](#5.3 缓存预热任务)
    • [5.4 批量数据处理](#5.4 批量数据处理)
  • 六、常见问题与解决方案
    • [6.1 连接问题](#6.1 连接问题)
    • [6.2 任务执行问题](#6.2 任务执行问题)
    • [6.3 性能问题](#6.3 性能问题)
    • [6.4 真实部署踩坑记录](#6.4 真实部署踩坑记录) 🆕
  • 七、学习资源推荐
  • 附录A:完整配置参考
  • [附录B:Cron 表达式速查表](#附录B:Cron 表达式速查表)
  • 附录C:版本差异对照表

一、基础认知篇

1.1 什么是 XXL-JOB?

XXL-JOB ("XXL" 取自作者许雪里(Xu Xue Li)姓名首字母)是一个分布式任务调度平台 ,其核心设计目标是:开发迅速、学习简单、轻量级、易扩展

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                     XXL-JOB 平台定位                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐  │
│   │ 数据同步  │    │ 报表生成  │    │ 缓存刷新  │    │ 消息推送  │  │
│   │ MySQL→ES │    │ 日/周/月  │    │ Redis    │    │ 钉钉/邮件 │  │
│   └────┬─────┘    └────┬─────┘    └────┬─────┘    └────┬─────┘  │
│        │               │               │               │        │
│        └───────────────┼───────────────┼───────────────┘        │
│                        │               │                        │
│                   ┌────▼───────────────▼────┐                   │
│                   │     XXL-JOB 调度中心     │                   │
│                   │  统一管理·调度·监控·告警  │                   │
│                   └─────────────────────────┘                   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
核心特性一览
特性 说明 优势
简单易用 通过 Web 页面操作,无需复杂配置 10分钟即可上手
动态管理 支持动态添加、修改、删除任务 无需重启服务
分布式调度 支持集群部署,保证调度中心 HA 消除单点故障
弹性扩容 执行器集群支持动态上下线 随业务量弹性伸缩
任务分片 支持分片广播,实现并行处理 大数据量高效处理
失败重试 支持任务失败自动重试 提高任务成功率
监控告警 邮件/钉钉告警,实时掌握任务状态 故障及时发现
GLUE 模式 支持在线编辑代码,实时生效 开发效率高
路由策略 轮询、随机、一致性哈希、最不经常使用等 灵活调度
XXL-JOB vs 其他调度框架
对比维度 XXL-JOB Elastic-Job Quartz Spring @Scheduled
调度中心 ✅ 独立 Admin ✅ 基于 ZK ❌ 无 ❌ 无
可视化管理 ✅ 完善 Web UI ⚠️ 需集成 ❌ 无 GUI ❌ 无
集群高可用 ✅ DB 锁 ✅ ZK 选举 ⚠️ 需 JDBC ❌ 单点
任务分片 ✅ 支持 ✅ 支持 ❌ 需自行实现 ❌ 不支持
失败重试 ✅ 内置 ✅ 内置 ⚠️ 需配置 ❌ 不支持
GLUE 在线编辑 ✅ 支持 ❌ 不支持 ❌ 不支持 ❌ 不支持
运维成本 🟢 低 🟡 中(需 ZK) 🟡 中 🟢 极低
社区活跃度 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐

小结 :对于大多数微服务架构下的定时任务场景,XXL-JOB 在功能完整性运维简便性上达到了很好的平衡,这也是它成为国内最流行的分布式任务调度框架的原因。


1.2 为什么需要分布式任务调度?

传统定时任务的问题
复制代码
┌─────────────────────────────────────────────────────────────────┐
│                 传统定时任务困境 vs 分布式调度解决方案                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   问题1: 单点故障风险                                             │
│   ┌─────────────────────┐        ┌─────────────────────────────┐ │
│   │  @Scheduled(cron)   │  ❌    │  调度中心集群 + 执行器集群      │ │
│   │  单台机器挂了→任务停  │  ──▶  │  任一节点故障不影响整体          │ │
│   └─────────────────────┘        └─────────────────────────────┘ │
│                                                                 │
│   问题2: 任务执行不均                                             │
│   ┌─────────────────────┐        ┌─────────────────────────────┐ │
│   │  所有任务挤在一台机器 │  ❌   │  路由策略分散到多台执行器        │ │
│   │  CPU/内存 过载       │  ──▶  │  负载均衡 + 弹性扩容             │ │
│   └─────────────────────┘        └─────────────────────────────┘ │
│                                                                 │
│   问题3: 缺乏监控告警                                             │
│   ┌─────────────────────┐        ┌─────────────────────────────┐ │
│   │  任务失败没人知道     │  ❌   │  实时监控 + 邮件/钉钉告警        │ │
│   │  只能靠人工巡检       │  ──▶  │  任务失败自动通知               │ │
│   └─────────────────────┘        └─────────────────────────────┘ │
│                                                                 │
│   问题4: 难以扩展                                                │
│   ┌─────────────────────┐        ┌─────────────────────────────┐ │
│   │  业务增长→单机扛不住  │  ❌   │  执行器动态注册/注销             │ │
│   │  改代码→重启→风险     │  ──▶  │  秒级弹性伸缩                   │ │
│   └─────────────────────┘        └─────────────────────────────┘ │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
微服务架构下的核心需求

在微服务架构中,每个服务都可能包含定时任务,XXL-JOB 提供了统一的调度入口

复制代码
                    ┌──────────────────┐
                    │   XXL-JOB Admin   │  ← 统一调度中心(Web管理)
                    │  (调度中心集群)     │
                    └──────┬───────────┘
                           │ HTTP/RPC 通信
               ┌───────────┼───────────────┐
               │           │               │
        ┌──────▼──────┐ ┌──▼────────┐ ┌───▼──────────┐
        │  订单服务     │ │  用户服务  │ │  报表服务      │
        │  Executor-1  │ │ Executor-2│ │  Executor-3   │
        │  :9999       │ │  :9998    │ │  :9997        │
        └──────────────┘ └───────────┘ └───────────────┘

1.3 核心架构组件

XXL-JOB 采用中心化调度设计,由两大核心组件构成:

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    XXL-JOB 核心架构全景图                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                   调度中心 (xxl-job-admin)                 │  │
│  │                                                          │  │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐    │  │
│  │  │ 任务管理  │ │ 调度触发  │ │ 执行器管理 │ │ 日志管理  │    │  │
│  │  │ CRUD/启停 │ │ Cron解析 │ │ 注册发现  │ │ 日志查询  │    │  │
│  │  └──────────┘ └──────────┘ └──────────┘ └──────────┘    │  │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐    │  │
│  │  │ 告警中心  │ │ 权限控制  │ │ 路由策略  │ │ 用户管理  │    │  │
│  │  │ 邮件/钉钉 │ │ AccessToken│ │ 10+种策略 │ │ 角色权限  │    │  │
│  │  └──────────┘ └──────────┘ └──────────┘ └──────────┘    │  │
│  │                                                          │  │
│  │  存储层: MySQL 数据库(任务配置、执行日志、调度锁)            │  │
│  └───────────────────────┬──────────────────────────────────┘  │
│                          │                                      │
│                          │ HTTP 通信 (调度请求/心跳/回调)          │
│                          │                                      │
│  ┌───────────────────────┴──────────────────────────────────┐  │
│  │                   执行器集群 (xxl-job-executor)            │  │
│  │                                                          │  │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐    │  │
│  │  │  Executor-1  │  │  Executor-2  │  │  Executor-3  │    │  │
│  │  │  port: 9999  │  │  port: 9999  │  │  port: 9999  │    │  │
│  │  │  JobHandler  │  │  JobHandler  │  │  JobHandler  │    │  │
│  │  │  ├─ job1     │  │  ├─ job1     │  │  ├─ job1     │    │  │
│  │  │  ├─ job2     │  │  ├─ job2     │  │  ├─ job2     │    │  │
│  │  │  └─ job3     │  │  └─ job3     │  │  └─ job3     │    │  │
│  │  └──────────────┘  └──────────────┘  └──────────────┘    │  │
│  │                                                          │  │
│  │  注册方式: 自动注册(心跳维持)/ 手动录入                      │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
三大核心概念详解
概念 英文 职责 类比
调度中心 Admin / Scheduler 管理任务、触发调度、收集结果、发送告警 指挥官
执行器 Executor 接收调度指令、执行任务逻辑、回报结果 士兵
任务 Job / JobHandler 具体的业务逻辑单元,由 @XxlJob 注解标识 作战任务
调度流程(一次完整的任务执行)
复制代码
  时间轴
  ─────────────────────────────────────────────────────────────────▶

  [调度中心]                          [执行器]
     │                                  │
     │  ① Cron 表达式触发时间到           │
     │  ┌─ 查询待执行任务列表              │
     │  ├─ 获取执行器地址(路由策略)       │
     │  └─ 构建调度请求                   │
     │                                  │
     │  ② 发送 HTTP 调度请求 ──────────▶ │
     │                                  │  ③ 接收请求
     │                                  │  ├─ 解析任务ID+参数
     │                                  │  ├─ 匹配 JobHandler
     │                                  │  └─ 创建执行线程
     │                                  │
     │                                  │  ④ 执行业务逻辑
     │                                  │  ├─ XxlJobHelper.log()
     │                                  │  └─ 异常处理
     │                                  │
     │  ⑤ 接收执行结果 ◀──────────────── │  ⑥ 回调结果
     │  ├─ 记录执行日志                   │  └─ handleSuccess/Fail
     │  ├─ 判断是否需要重试               │
     │  ├─ 触发子任务(如有)             │
     │  └─ 发送告警(如失败)             │
     │                                  │

二、环境准备篇

2.1 前期准备

实操1:环境检查清单

在开始 XXL-JOB 之旅前,请确保以下环境已就绪:

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                      环境检查清单                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ✅ JDK 8 / JDK 17                                              │
│     → java -version                                             │
│     → 推荐: JDK 17 (XXL-JOB 2.5.0+ 强制要求)                      │
│     → 兼容: JDK 8  (XXL-JOB 2.4.x 及以下)                        │
│                                                                 │
│  ✅ Maven 3.6+                                                  │
│     → mvn -version                                              │
│                                                                 │
│  ✅ MySQL 5.7+ / MySQL 8.0                                      │
│     → mysql -V                                                  │
│     → 需要创建 xxl_job 数据库                                     │
│                                                                 │
│  ✅ Git (可选,用于克隆源码)                                       │
│     → git --version                                             │
│                                                                 │
│  ✅ IDE: IntelliJ IDEA / Eclipse                                │
│     → 推荐 IDEA Community/Ultimate                              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

环境验证命令

bash 复制代码
# JDK 版本检查
java -version
# 期望输出: openjdk version "17.0.x" 或 "1.8.0_xxx"

# Maven 版本检查
mvn -version
# 期望输出: Apache Maven 3.6.x+

# MySQL 连接检查
mysql -u root -p -e "SELECT VERSION();"
# 期望输出: 5.7.x 或 8.0.x
实操2:源码下载与项目结构
bash 复制代码
# ========================================
# 方式一:克隆 JDK 17 最新版本(推荐,2.5.0+)
# ========================================
git clone https://gitee.com/xuxueli0323/xxl-job.git
cd xxl-job

# ========================================
# 方式二:克隆 JDK 8 兼容版本(2.4.x)
# ========================================
git clone -b 2.4.2 https://gitee.com/xuxueli0323/xxl-job.git xxl-job-2.4.2

2.2 项目结构解析

复制代码
xxl-job/
├── xxl-job-admin/              ← 调度中心(核心模块)
│   ├── src/main/java/
│   │   └── com/xxl/job/admin/
│   │       ├── controller/     ← Web 控制器
│   │       │   ├── JobInfoController.java     ← 任务管理
│   │       │   ├── JobGroupController.java    ← 执行器管理
│   │       │   ├── JobLogController.java      ← 日志查看
│   │       │   └── UserController.java        ← 用户管理
│   │       ├── core/
│   │       │   ├── scheduler/  ← 调度引擎 (MisfireStrategy, etc.)
│   │       │   ├── trigger/    ← 触发器 (XxlJobTrigger)
│   │       │   ├── route/      ← 路由策略 (ExecutorRouter)
│   │       │   └── thread/     ← 调度线程
│   │       └── dao/            ← 数据访问层 (MyBatis)
│   └── src/main/resources/
│       ├── application.properties       ← 核心配置
│       ├── mybatis-mapper/              ← SQL 映射
│       └── static/                      ← 前端资源 (Layui)
│
├── xxl-job-core/               ← 公共核心依赖(执行器引用此模块)
│   └── src/main/java/
│       └── com/xxl/job/core/
│           ├── biz/
│           │   ├── ExecutorBiz.java      ← 执行器接口
│           │   └── AdminBiz.java         ← 调度中心接口
│           ├── handler/
│           │   └── IJobHandler.java      ← 任务处理器抽象
│           ├── executor/
│           │   └── XxlJobExecutor.java   ← 执行器核心
│           └── thread/                   ← 线程模型
│
├── xxl-job-executor-samples/    ← 执行器示例(参考项目)
│   ├── xxl-job-executor-sample-springboot/  ← Spring Boot 版本 ★
│   └── xxl-job-executor-sample-frameless/   ← 无框架版本
│
└── doc/                         ← 文档
    ├── db/
    │   └── tables_xxl_job.sql   ← 数据库初始化脚本 ★
    └── images/                  ← 文档截图

提示 : 上机实战中,我们主要操作 xxl-job-admin (调度中心)和参考 xxl-job-executor-sample-springboot(执行器)。


三、上机实战篇

3.1 调度中心搭建

实操3:数据库初始化

Step 1 - 连接 MySQL 并创建数据库:

sql 复制代码
-- 登录 MySQL
mysql -u root -p

-- 创建数据库(UTF-8 字符集)
CREATE DATABASE IF NOT EXISTS xxl_job
    DEFAULT CHARACTER SET utf8mb4
    DEFAULT COLLATE utf8mb4_unicode_ci;

-- 查看是否创建成功
SHOW DATABASES LIKE 'xxl_job';

Step 2 - 执行初始化脚本:

bash 复制代码
# 进入项目目录
cd xxl-job/doc/db

# 执行建表脚本
mysql -u root -p xxl_job < tables_xxl_job.sql

Step 3 - 验证表结构:

sql 复制代码
USE xxl_job;
SHOW TABLES;

-- 预期输出:
-- +------------------------+
-- | Tables_in_xxl_job      |
-- +------------------------+
-- | xxl_job_group          |  ← 执行器组
-- | xxl_job_info           |  ← 任务信息
-- | xxl_job_lock           |  ← 分布式锁
-- | xxl_job_log            |  ← 调度日志
-- | xxl_job_log_report     |  ← 日志报表
-- | xxl_job_logglue        |  ← GLUE 日志
-- | xxl_job_registry       |  ← 执行器注册表
-- | xxl_job_user           |  ← 用户表
-- +------------------------+

核心表说明

表名 用途 关键字段
xxl_job_group 执行器组信息 app_name, title, address_type, address_list
xxl_job_info 任务定义 job_group, job_desc, executor_handler, schedule_conf
xxl_job_lock 调度中心分布式锁 lock_name(保证集群只有一个节点调度)
xxl_job_log 任务执行日志 job_id, trigger_time, handle_time, trigger_code
xxl_job_registry 执行器注册信息 registry_group, registry_key, registry_value
xxl_job_user 用户账号 username, password, role
实操4:配置调度中心

编辑 xxl-job-admin/src/main/resources/application.properties(或 application.yml):

properties 复制代码
# ============================================
# 1. 服务端口
# ============================================
server.port=8080
server.servlet.context-path=/xxl-job-admin

# ============================================
# 2. 数据库配置(★必须修改★)
# ============================================
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=your_password_here
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# HikariCP 连接池配置
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.maximum-pool-size=30
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=HikariCP-XXL-JOB
spring.datasource.hikari.max-lifetime=900000
spring.datasource.hikari.connection-timeout=10000
spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.hikari.validation-timeout=1000

# ============================================
# 3. 邮件告警配置
# ============================================
spring.mail.host=smtp.qq.com
spring.mail.port=25
spring.mail.username=your_email@qq.com
spring.mail.password=your_qq_smtp_auth_code  # QQ邮箱需用授权码,非登录密码
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
spring.mail.from=your_email@qq.com

# ============================================
# 4. 调度中心核心参数
# ============================================
# 调度中心 AccessToken(执行器需匹配)
xxl.job.accessToken=

# 调度中心国际化设置 (zh_CN / en)
xxl.job.i18n=zh_CN

# 调度线程池最大线程数
xxl.job.triggerpool.fast.max=200
xxl.job.triggerpool.slow.max=100

# 日志保留天数
xxl.job.logretentiondays=30

配置参数详解

参数 含义 建议值
server.port Admin Web 端口 8080
spring.datasource.url 数据库连接 根据实际修改
spring.datasource.hikari.maximum-pool-size 连接池大小 30(小型)/ 100(大型)
spring.mail.host 邮件服务器 smtp.qq.com / smtp.163.com
xxl.job.accessToken 调度中心认证令牌 生产环境必须设置
xxl.job.triggerpool.fast.max 快线程池大小 200(默认)
xxl.job.triggerpool.slow.max 慢线程池大小 100(默认)
xxl.job.logretentiondays 日志保留天数 30

踩坑提醒 : QQ 邮箱的 spring.mail.password 不是 QQ 登录密码,而是开启 SMTP 服务后生成的授权码

实操5:启动调度中心

方式一:IDEA 直接运行

复制代码
1. 打开 IDEA → Import Project → 选择 xxl-job 目录
2. 等待 Maven 依赖下载完成
3. 找到 com.xxl.job.admin.XxlJobAdminApplication
4. 右键 → Run 'XxlJobAdminApplication'
5. 观察控制台输出:
   → Starting XxlJobAdminApplication ...
   → Started XxlJobAdminApplication in 8.5 seconds

方式二:Maven 打包 + JAR 运行

bash 复制代码
# 在 xxl-job 根目录下
cd xxl-job

# 打包(跳过测试)
mvn clean package -DskipTests

# 运行调度中心
java -jar xxl-job-admin/target/xxl-job-admin-2.5.0.jar

# 或者指定端口
java -jar xxl-job-admin/target/xxl-job-admin-2.5.0.jar --server.port=8081

验证启动

复制代码
浏览器访问: http://localhost:8080/xxl-job-admin

默认账号: admin
默认密码: 123456

期望看到: XXL-JOB 调度中心管理界面(Layui 风格)

┌─────────────────────────────────────────────────────────────────┐
│                    XXL-JOB 调度中心登录成功                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  左侧导航菜单:                                                    │
│  ├─ 运行报表    → 任务执行统计概览                                  │
│  ├─ 任务管理    → 任务 CRUD + 启停 + 手动触发                       │
│  ├─ 调度日志    → 任务执行历史 + 日志详情                            │
│  ├─ 执行器管理  → 执行器注册/配置                                   │
│  ├─ 用户管理    → 用户 + 角色管理                                   │
│  └─ 使用教程    → 内置文档                                        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

3.2 执行器开发实战

实操6:创建 Spring Boot 执行器项目

项目结构

复制代码
xxl-job-executor-demo/
├── pom.xml
└── src/main/
    ├── java/com/example/xxljob/
    │   ├── XxlJobDemoApplication.java      ← 启动类
    │   ├── config/
    │   │   └── XxlJobConfig.java           ← XXL-JOB 配置类
    │   └── job/
    │       ├── SimpleJob.java              ← 简单任务
    │       ├── AdvancedJob.java            ← 高级任务
    │       └── DataSyncJob.java            ← 数据同步任务
    └── resources/
        ├── application.yml                 ← 主配置
        └── logback.xml                     ← 日志配置

Step 1 - pom.xml 添加依赖:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>xxl-job-executor-demo</artifactId>
    <version>1.0.0</version>
    <name>xxl-job-executor-demo</name>

    <properties>
        <java.version>1.8</java.version>
        <xxl-job.version>2.5.0</xxl-job.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- XXL-JOB 核心依赖 ★★★ -->
        <dependency>
            <groupId>com.xuxueli</groupId>
            <artifactId>xxl-job-core</artifactId>
            <version>${xxl-job.version}</version>
        </dependency>

        <!-- Lombok (可选) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- MySQL (如需在任务中操作数据库) -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
实操7:配置执行器

src/main/resources/application.yml

yaml 复制代码
server:
  port: 8081  # 执行器自身端口(与 Admin 不同即可)

# ============================================
# XXL-JOB 执行器配置
# ============================================
xxl:
  job:
    # --- 调度中心配置 ---
    admin:
      # 调度中心地址(集群时逗号分隔)
      addresses: http://127.0.0.1:8080/xxl-job-admin

    # --- 执行器配置 ---
    executor:
      # 执行器 AppName(与 Admin 上配置的 AppName 一致)
      appname: xxl-job-executor-demo
      # 执行器地址(为空则自动获取本机 IP)
      address:
      # 执行器 IP(为空则自动获取,多网卡时建议手动指定)
      ip:
      # 执行器端口(默认 9999,与内嵌 Tomcat port 不同)
      port: 9999
      # 执行器运行日志存储路径
      logpath: /data/applogs/xxl-job/jobhandler
      # 日志保留天数
      logretentiondays: 30

    # --- 安全配置 ---
    # 需与调度中心 accessToken 一致
    accessToken: default_token

配置参数详解

参数 含义 注意事项
xxl.job.admin.addresses 调度中心地址 集群时用逗号分隔多个 URL
xxl.job.executor.appname 执行器标识名 必须与 Admin "执行器管理"中的 AppName 完全一致
xxl.job.executor.ip 执行器 IP 多网卡环境必须手动指定,否则可能注册错误 IP
xxl.job.executor.port 执行器通信端口 不与 server.port 冲突;集群中每个实例可用相同端口
xxl.job.executor.logpath 日志目录 确保有写入权限
xxl.job.accessToken 认证令牌 生产环境必须配置,与 Admin 一致
实操8:配置类编写

src/main/java/com/example/xxljob/config/XxlJobConfig.java

java 复制代码
package com.example.xxljob.config;

import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * XXL-JOB 执行器配置类
 *
 * 功能:
 *   1. 创建 XxlJobSpringExecutor Bean
 *   2. 自动注册到调度中心
 *   3. 通过 @XxlJob 注解发现任务处理器
 *
 * @author xxl-job-demo
 */
@Configuration
public class XxlJobConfig {

    private static final Logger log = LoggerFactory.getLogger(XxlJobConfig.class);

    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;

    @Value("${xxl.job.accessToken:}")
    private String accessToken;

    @Value("${xxl.job.executor.appname}")
    private String appname;

    @Value("${xxl.job.executor.address:}")
    private String address;

    @Value("${xxl.job.executor.ip:}")
    private String ip;

    @Value("${xxl.job.executor.port}")
    private int port;

    @Value("${xxl.job.executor.logpath}")
    private String logPath;

    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        log.info(">>>>>>>>>>> xxl-job executor config init.");

        XxlJobSpringExecutor executor = new XxlJobSpringExecutor();

        // === 基础配置 ===
        executor.setAdminAddresses(adminAddresses);
        executor.setAppname(appname);
        executor.setAddress(address);
        executor.setIp(ip);
        executor.setPort(port);

        // === 安全 ===
        if (accessToken != null && !accessToken.isEmpty()) {
            executor.setAccessToken(accessToken);
        }

        // === 日志 ===
        executor.setLogPath(logPath);
        executor.setLogRetentionDays(logRetentionDays);

        return executor;
    }
}

Spring Boot 启动类

java 复制代码
package com.example.xxljob;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class XxlJobDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(XxlJobDemoApplication.class, args);
        System.out.println("========================================");
        System.out.println("  XXL-JOB Executor Demo Started!");
        System.out.println("========================================");
    }
}

启动并验证

bash 复制代码
# 运行执行器
mvn spring-boot:run

# 期望日志输出:
# >>>>>>>>>>> xxl-job executor config init.
# >>>>>>>>>>> xxl-job, job executor start success.
# >>>>>>>>>>> xxl-job executor registry success, registryKey:xxl-job-executor-demo

3.3 任务开发实战

实操9:简单任务开发(BEAN 模式)

src/main/java/com/example/xxljob/job/SimpleJob.java

java 复制代码
package com.example.xxljob.job;

import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;

/**
 * 简单任务演示
 *
 * 包含:
 *   1. 基础 BEAN 模式任务
 *   2. 获取任务参数
 *   3. 分片参数获取
 *   4. 日志输出
 */
@Component
public class SimpleJob {

    private static final Logger log = LoggerFactory.getLogger(SimpleJob.class);

    /**
     * 1、基础任务 ------ 最简单的 BEAN 模式任务
     *
     * @XxlJob("demoJobHandler") 中的值是 JobHandler 名称,
     * 在 Admin 上配置任务时需要填写此名称。
     */
    @XxlJob("demoJobHandler")
    public void demoJobHandler() throws Exception {
        // ========================================
        // ① 获取任务参数
        // ========================================
        String jobParam = XxlJobHelper.getJobParam();
        XxlJobHelper.log("【基础任务】开始执行");
        XxlJobHelper.log("任务参数: {}", jobParam);

        // ========================================
        // ② 获取分片参数(即使不使用也可以获取)
        // ========================================
        int shardIndex = XxlJobHelper.getShardIndex();   // 当前分片序号(从0开始)
        int shardTotal = XxlJobHelper.getShardTotal();   // 总分片数
        XxlJobHelper.log("分片信息: 当前分片={}, 总分片={}", shardIndex, shardTotal);

        // ========================================
        // ③ 执行业务逻辑
        // ========================================
        String now = LocalDateTime.now()
                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

        XxlJobHelper.log("业务处理时间: {}", now);

        // 模拟业务处理耗时
        TimeUnit.SECONDS.sleep(2);

        XxlJobHelper.log("【基础任务】执行完成");

        // 默认成功(方法正常结束即为成功)
    }

    /**
     * 2、有参任务 ------ 根据参数执行不同逻辑
     */
    @XxlJob("paramJobHandler")
    public void paramJobHandler() throws Exception {
        String jobParam = XxlJobHelper.getJobParam();
        XxlJobHelper.log("【有参任务】接收到参数: {}", jobParam);

        // 根据参数执行不同分支
        switch (jobParam) {
            case "sync_user":
                syncUsers();
                break;
            case "sync_order":
                syncOrders();
                break;
            case "clean_cache":
                cleanCache();
                break;
            default:
                XxlJobHelper.handleFail("未知的任务类型: " + jobParam);
                return;
        }

        XxlJobHelper.handleSuccess("【有参任务】执行成功,参数=" + jobParam);
    }

    private void syncUsers() {
        XxlJobHelper.log(">>> 同步用户数据...");
        // 实际业务逻辑
    }

    private void syncOrders() {
        XxlJobHelper.log(">>> 同步订单数据...");
        // 实际业务逻辑
    }

    private void cleanCache() {
        XxlJobHelper.log(">>> 清理缓存...");
        // 实际业务逻辑
    }
}
实操10:复杂任务开发(分片 + 失败重试)

src/main/java/com/example/xxljob/job/AdvancedJob.java

java 复制代码
package com.example.xxljob.job;

import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 高级任务演示
 *
 * 包含:
 *   1. 分片广播任务
 *   2. 失败重试任务
 *   3. 子任务链
 *   4. 超时控制
 */
@Component
public class AdvancedJob {

    private static final Logger log = LoggerFactory.getLogger(AdvancedJob.class);

    // 模拟数据源(实际场景可能是数据库查询结果)
    private static final int TOTAL_RECORDS = 10000;

    // 断点续传进度记录
    private static final ConcurrentHashMap<String, AtomicInteger> progressMap =
            new ConcurrentHashMap<>();

    /**
     * 1、分片广播任务
     *
     * 使用场景:
     *   - 大数据量批量处理(如 100万 条数据分 10 片并行处理)
     *   - 多台机器同时处理不同数据段
     *
     * 路由策略必须选择: "分片广播"
     */
    @XxlJob("shardingJobHandler")
    public void shardingJobHandler() throws Exception {
        // 获取分片参数
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();

        XxlJobHelper.log("【分片广播任务】启动: 分片 {}/{}", shardIndex + 1, shardTotal);

        // ========================================
        // 分片算法:每个分片处理属于自己的数据
        // 原理: 数据ID % shardTotal == shardIndex
        // ========================================
        int processedCount = 0;
        int failedCount = 0;

        for (int i = 0; i < TOTAL_RECORDS; i++) {
            // 分片判定:当前数据属于本分片
            if (i % shardTotal == shardIndex) {
                try {
                    // 模拟处理一条数据
                    processRecord(i);
                    processedCount++;
                } catch (Exception e) {
                    failedCount++;
                    XxlJobHelper.log("记录 {} 处理失败: {}", i, e.getMessage());
                }
            }
        }

        String result = String.format(
                "【分片广播任务】完成: 分片=%d/%d, 处理=%d, 失败=%d",
                shardIndex + 1, shardTotal, processedCount, failedCount
        );

        XxlJobHelper.log(result);

        if (failedCount > 0) {
            XxlJobHelper.handleFail(result);
        } else {
            XxlJobHelper.handleSuccess(result);
        }
    }

    /**
     * 模拟处理单条记录
     */
    private void processRecord(int id) throws Exception {
        // 模拟大部分成功,少量失败
        if (id % 1007 == 0) {  // 约 1% 失败率
            throw new Exception("数据异常: id=" + id);
        }
        // 业务处理逻辑
    }

    /**
     * 2、失败重试任务
     *
     * 配置要求:
     *   - Admin 上配置 "失败重试次数"(如 3 次)
     *   - 在 catch 中重新抛出异常(调度中心才会触发重试)
     */
    @XxlJob("retryJobHandler")
    public void retryJobHandler() throws Exception {
        XxlJobHelper.log("【重试任务】开始执行, 请确保在Admin上配置了失败重试次数");

        try {
            String jobParam = XxlJobHelper.getJobParam();

            // ========================================
            // 核心业务逻辑
            // ========================================
            boolean result = callExternalApi(jobParam);

            if (!result) {
                // 注意: 必须 throw Exception 才能触发重试机制!
                // 如果只调用 handleFail 但方法正常返回,不会重试
                throw new RuntimeException("外部API调用失败,参数=" + jobParam);
            }

            XxlJobHelper.handleSuccess("【重试任务】成功");
        } catch (Exception e) {
            XxlJobHelper.log("【重试任务】执行异常: {}", e.getMessage());
            // 记录失败原因,抛出异常触发重试
            XxlJobHelper.handleFail(e.getMessage());
            throw e;  // ← 关键: 必须抛出异常才能触发调度中心的重试机制
        }
    }

    /**
     * 模拟调用外部 API(可能失败的场景)
     */
    private boolean callExternalApi(String param) {
        // 模拟:参数包含 "success" 时成功
        return param != null && param.contains("success");
    }

    /**
     * 3、断点续传任务
     *
     * 使用场景:
     *   - 大批量数据处理,意外中断后可从上次进度继续
     *   - 避免重复处理已处理的数据
     */
    @XxlJob("checkpointJobHandler")
    public void checkpointJobHandler() throws Exception {
        String jobKey = "data_migration";
        AtomicInteger progress = progressMap.computeIfAbsent(
                jobKey, k -> new AtomicInteger(0));

        int startIndex = progress.get();
        int batchSize = 100;

        XxlJobHelper.log("【断点续传】从索引 {} 开始,批量大小 {}", startIndex, batchSize);

        for (int i = startIndex; i < startIndex + batchSize && i < TOTAL_RECORDS; i++) {
            // 处理数据...
            // 每处理一条更新进度
            progress.set(i + 1);
        }

        int currentProgress = progress.get();
        XxlJobHelper.log("【断点续传】当前进度: {}/{}", currentProgress, TOTAL_RECORDS);

        if (currentProgress >= TOTAL_RECORDS) {
            progressMap.remove(jobKey);  // 完成后清理
            XxlJobHelper.handleSuccess("【断点续传】全部完成");
        } else {
            XxlJobHelper.handleSuccess(
                String.format("【断点续传】进度 %d/%d,等待下次执行", currentProgress, TOTAL_RECORDS)
            );
        }
    }
}
任务开发要点总结
要点 说明 代码示例
获取参数 XxlJobHelper.getJobParam() 在 Admin 任务配置中填写参数
输出日志 XxlJobHelper.log("msg {}", arg) 日志会上报到 Admin 查看
成功返回 XxlJobHelper.handleSuccess("msg") 可选的,默认方法正常结束=成功
失败返回 XxlJobHelper.handleFail("msg") + throw 两者都需要才能触发重试
分片参数 getShardIndex() / getShardTotal() 仅在路由策略为"分片广播"时有效

3.4 Web 界面操作实战

实操11:执行器管理配置

登录调度中心 http://localhost:8080/xxl-job-admin,进入 执行器管理新增

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    新增执行器 - 配置表单                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  AppName:        xxl-job-executor-demo                          │
│                  ↑ 必须与执行器 application.yml 中的              │
│                    xxl.job.executor.appname 完全一致!            │
│                                                                 │
│  名称:           示例执行器                                       │
│                  ↑ 管理界面的显示名称                              │
│                                                                 │
│  注册方式:       ● 自动注册                                       │
│                  ○ 手动录入                                       │
│                  ↑ 推荐使用自动注册,执行器启动后自动发现            │
│                                                                 │
│  机器地址:       (留空)                                          │
│                  ↑ 自动注册模式下留空即可                          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

注册方式对比

对比项 自动注册 手动录入
配置复杂度 🟢 低 🟡 中
执行器上下线 自动感知(心跳30s) 需手动修改
适用场景 动态扩容/缩容 固定 IP 环境
推荐度 ⭐⭐⭐⭐⭐ ⭐⭐⭐
实操12:任务配置详解

进入 任务管理新增

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    新增任务 - 配置表单                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─ 基础信息 ─────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  执行器:     [▼ 示例执行器]                                  │ │
│  │  任务描述:    用户数据同步任务                                │ │
│  │  负责人:      admin                                        │ │
│  │  报警邮件:    admin@example.com (多个用逗号分隔)              │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                 │
│  ┌─ 调度配置 ─────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  调度类型:    ● CRON  ○ 固定间隔(秒)                        │ │
│  │  Cron:       0 0 2 * * ?  ← 每天凌晨2点                      │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                 │
│  ┌─ 任务配置 ─────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  运行模式:    ● BEAN  ○ GLUE(Java)  ○ GLUE(Shell)...       │ │
│  │  JobHandler: demoJobHandler                                │ │
│  │              ↑ 必须与代码中 @XxlJob("xxx") 注解一致           │ │
│  │                                                            │ │
│  │  任务参数:    type=data_sync                                │ │
│  │              ↑ 可通过 XxlJobHelper.getJobParam() 获取        │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                 │
│  ┌─ 高级配置 ─────────────────────────────────────────────────┐ │
│  │                                                            │ │
│  │  路由策略:    [▼ 轮询]                                       │ │
│  │  子任务ID:    留空(如需任务链可填写)                         │ │
│  │  阻塞处理策略: [▼ 单机串行]                                   │ │
│  │  任务超时时间: 0(0=不限制,单位秒)                           │ │
│  │  失败重试次数: 3                                             │ │
│  │  失败重试间隔: 60(秒,仅 GLUE 模式有效)                      │ │
│  │  任务描述:    详细描述...                                     │ │
│  │                                                            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
核心配置参数详解

① 运行模式对比

模式 说明 适用场景
BEAN 在 Java 代码中编写,部署到执行器 正式项目,推荐使用
GLUE(Java) 在 Admin Web 界面在线写 Java 代码 开发调试、临时任务
GLUE(Shell) 在线编写 Shell 脚本 服务器运维任务
GLUE(Python) 在线编写 Python 脚本 数据处理、脚本任务
GLUE(PHP) 在线编写 PHP 脚本 PHP 项目
GLUE(NodeJS) 在线编写 Node.js 脚本 前端/Node 项目
GLUE(PowerShell) 在线编写 PowerShell 脚本 Windows 环境

② 路由策略对比

策略 说明 使用场景
第一个 固定选择第一个执行器 单执行器或指定执行器
最后一个 固定选择最后一个执行器 需要固定某台机器
轮询 依次分配到每个执行器 负载均衡,推荐
随机 随机选择一个执行器 简单负载均衡
一致性哈希 相同任务参数调度到同一执行器 需要粘性调度的场景
最不经常使用 选使用频率最低的执行器 负载均衡
最近最久未使用 选择最久未使用的执行器 负载均衡
故障转移 按顺序尝试,失败则下一个 高可用场景
忙碌转移 跳过正在执行任务的执行器 避免任务堆积
分片广播 所有执行器都执行,分片处理 大数据量并行处理

③ 阻塞处理策略

策略 说明 行为
单机串行 当前任务未完成时,新调度丢弃 默认,保证执行顺序
丢弃后续调度 运行中则跳过 不关心遗漏
覆盖之前调度 终止当前运行,执行新的 只关心最新数据
实操13:任务运行与监控

任务操作按钮说明

复制代码
┌─────────────────────────────────────────────────────────────────┐
│  任务列表操作栏                                                  │
│                                                                 │
│  [启动] ─ 启用 Cron 自动调度                                     │
│  [暂停] ─ 暂停自动调度(任务仍可手动触发)                          │
│  [执行一次] ─ 手动触发一次执行                                    │
│  [查询日志] ─ 查看执行历史                                        │
│  [编辑]   ─ 修改任务配置                                         │
│  [删除]   ─ 删除任务                                            │
│  [GLUE IDE] ─ 打开在线编辑器(仅 GLUE 模式可见)                   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

调度日志解读

复制代码
┌─────────────────────────────────────────────────────────────────┐
│  调度日志详情                                                    │
│                                                                 │
│  任务ID:     2                                                  │
│  执行器地址: http://192.168.1.100:9999/                          │
│  调度时间:   2026-06-03 10:00:00                                 │
│  调度结果:   成功 [调度成功]  ← 调度中心→执行器的网络通信结果       │
│  执行结果:   成功 [任务执行成功] ← 业务逻辑执行结果                 │
│  执行耗时:   2356ms                                              │
│                                                                 │
│  调度结果枚举:                                                    │
│    - 成功: 调度请求发送成功                                       │
│    - 失败: 网络不通/执行器未注册/AccessToken 不匹配                │
│                                                                 │
│  执行结果枚举:                                                    │
│    - 成功: 任务正常完成                                          │
│    - 失败: 业务逻辑抛出异常                                      │
│    - 超时: 超过配置的超时时间                                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

3.5 进阶功能实战

实操14:Cron 表达式完全指南

Cron 表达式由 7 个字段组成(XXL-JOB 支持到秒级):

复制代码
┌──────────── 秒 (0-59)
│ ┌────────── 分 (0-59)
│ │ ┌──────── 时 (0-23)
│ │ │ ┌────── 日 (1-31)
│ │ │ │ ┌──── 月 (1-12)
│ │ │ │ │ ┌── 周 (1-7, 1=周日, 7=周六)
│ │ │ │ │ │
* * * * * *

常用 Cron 表达式速查

场景 Cron 表达式 说明
每秒执行 * * * * * ? 调试专用,生产慎用
每5秒执行 0/5 * * * * ? 高频任务
每分钟执行 0 * * * * ? 常规频率
每5分钟执行 0 0/5 * * * ? 中频任务
每小时执行 0 0 * * * ? 低频任务
每天凌晨2点 0 0 2 * * ? 数据同步/备份
每天中午12点 0 0 12 * * ? 日报生成
每周一9点 0 0 9 ? * 2 周报生成
每月1号0点 0 0 0 1 * ? 月报生成
工作日9-18点每小时 0 0 9-18 ? * 2-6 工作时间执行
每年1月1日 0 0 0 1 1 ? 年度任务

Cron 特殊字符

字符 含义 示例
* 所有值 * * * * * ? 每秒
? 不指定(日/周期互斥) 日期和星期只能用 ? 占位
- 范围 9-18 9点到18点
, 枚举 1,3,5,7
/ 递增步长 0/15 每15个单位
L 最后 L 当月最后一天
W 最近工作日 15W 离15号最近的工作日
# 第几个 2#3 第3个周一
实操15:任务失败重试机制
java 复制代码
@Component
public class RetryJobDemo {

    private static final int MAX_MANUAL_RETRY = 3;

    /**
     * 带手动重试的任务
     *
     * 重试机制原理:
     *   1. Admin 配置 "失败重试次数"(如 3)
     *   2. 方法内抛异常→ Admin 感知失败→ 自动重试
     *   3. 可结合手动重试实现更灵活的策略
     */
    @XxlJob("smartRetryJobHandler")
    public void smartRetryJobHandler() throws Exception {
        String jobParam = XxlJobHelper.getJobParam();

        XxlJobHelper.log("【智能重试任务】开始, 参数={}", jobParam);

        int retryCount = 0;
        boolean success = false;

        while (retryCount < MAX_MANUAL_RETRY && !success) {
            try {
                // ==================================
                // 核心业务逻辑
                // ==================================
                success = executeBusinessLogic(jobParam);

                if (success) {
                    XxlJobHelper.log("【智能重试】第{}次重试成功", retryCount);
                    XxlJobHelper.handleSuccess();
                    return;
                }

                retryCount++;
                XxlJobHelper.log("【智能重试】第{}次重试失败,继续重试...", retryCount);

                // 递增等待时间(退避策略)
                long waitSeconds = retryCount * 10L;
                XxlJobHelper.log("【智能重试】等待{}秒后重试", waitSeconds);
                Thread.sleep(waitSeconds * 1000);

            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }

        // 所有重试都失败
        XxlJobHelper.log("【智能重试】经过{}次重试后仍然失败", MAX_MANUAL_RETRY);
        XxlJobHelper.handleFail("业务处理失败,已重试" + MAX_MANUAL_RETRY + "次");
        throw new RuntimeException("所有重试均失败");
    }

    private boolean executeBusinessLogic(String param) {
        // 模拟:参数为 "success" 时成功
        return "success".equals(param);
    }
}

重试机制要点

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    XXL-JOB 失败重试流程                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Admin 调度 ──▶ 执行器执行业务                                    │
│                    │                                            │
│                    ├── 成功 → handleSuccess() → 结束              │
│                    │                                            │
│                    └── 失败 → throw Exception                    │
│                              │                                  │
│               ┌──────────────▼─────────────────┐                │
│               │  Admin 检测到失败                 │                │
│               │  ├─ 重试次数 < 配置的重试次数?      │                │
│               │  │   ├─ YES → 等待 → 重新调度     │                │
│               │  │   └─ NO  → 标记最终失败         │                │
│               │  ├─ 发送告警邮件/钉钉              │                │
│               │  └─ 触发子任务(如有)              │                │
│               └────────────────────────────────┘                │
│                                                                 │
│  ★ 关键: 必须在代码中 throw Exception 才会触发重试!                │
│    仅调用 handleFail() 但不抛出异常,Admin 会认为是成功。           │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
实操16:任务依赖与子任务链

子任务配置

复制代码
在 Admin 任务管理中:
  1. 创建任务 A (数据同步) - ID=1
  2. 创建任务 B (数据校验) - ID=2
  3. 创建任务 C (发送报告) - ID=3

  配置任务 A → 子任务ID填 "2"
  配置任务 B → 子任务ID填 "3"

执行链路:
  任务A完成 → 自动触发任务B → 自动触发任务C
  (任何一环失败,后续任务不会触发)
java 复制代码
@Component
public class TaskChainJob {

    /**
     * 任务 A: 数据同步(父任务)
     */
    @XxlJob("dataSyncJob")
    public void dataSyncJob() throws Exception {
        XxlJobHelper.log("【任务A】开始数据同步...");

        // 同步逻辑
        boolean syncSuccess = performDataSync();

        if (syncSuccess) {
            XxlJobHelper.handleSuccess("数据同步成功,将自动触发校验任务");
        } else {
            XxlJobHelper.handleFail("数据同步失败");
            throw new RuntimeException("数据同步失败");
        }
    }

    /**
     * 任务 B: 数据校验(子任务,由任务A触发)
     */
    @XxlJob("dataVerifyJob")
    public void dataVerifyJob() throws Exception {
        XxlJobHelper.log("【任务B】开始数据校验...");

        boolean verifySuccess = performDataVerification();

        if (verifySuccess) {
            XxlJobHelper.handleSuccess("数据校验成功,将自动触发报告任务");
        } else {
            XxlJobHelper.handleFail("数据校验失败");
            throw new RuntimeException("数据校验失败");
        }
    }

    /**
     * 任务 C: 发送报告(子任务,由任务B触发)
     */
    @XxlJob("reportSendJob")
    public void reportSendJob() throws Exception {
        XxlJobHelper.log("【任务C】开始发送报告...");
        sendReport();
        XxlJobHelper.handleSuccess("报告发送完成");
    }

    // === 模拟方法 ===
    private boolean performDataSync() { return true; }
    private boolean performDataVerification() { return true; }
    private void sendReport() { /* 发送报告 */ }
}

3.6 集群部署实战

实操17:多执行器集群部署
复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    执行器集群架构                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ┌─────────────────────────────────────────────────────────┐  │
│   │              XXL-JOB Admin (调度中心)                     │  │
│   │              http://192.168.0.1:8080/xxl-job-admin       │  │
│   └──────────────────────┬──────────────────────────────────┘  │
│                          │                                      │
│            ┌─────────────┼─────────────┐                        │
│            │             │             │                        │
│   ┌────────▼──────┐ ┌───▼────────┐ ┌──▼──────────┐            │
│   │  Executor-1   │ │ Executor-2 │ │ Executor-3   │            │
│   │  192.168.0.11 │ │192.168.0.12│ │ 192.168.0.13 │            │
│   │  port: 9999   │ │ port: 9999 │ │ port: 9999   │            │
│   │  AppName:     │ │ AppName:   │ │ AppName:     │            │
│   │  xxl-job-demo │ │ xxl-job-demo│ │ xxl-job-demo │            │
│   └───────────────┘ └────────────┘ └──────────────┘            │
│                                                                 │
│   所有执行器的 AppName 必须相同,Admin 据此识别集群               │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

部署步骤

bash 复制代码
# ============================================
# 步骤1: Maven 打包
# ============================================
cd /path/to/xxl-job-executor-demo
mvn clean package -DskipTests

# ============================================
# 步骤2: 复制 JAR 到各台机器
# ============================================
scp target/xxl-job-executor-demo-1.0.0.jar user@192.168.0.11:/opt/xxl-job/
scp target/xxl-job-executor-demo-1.0.0.jar user@192.168.0.12:/opt/xxl-job/
scp target/xxl-job-executor-demo-1.0.0.jar user@192.168.0.13:/opt/xxl-job/

# ============================================
# 步骤3: 在各台机器上启动(注意: 每台机器的 IP 不同,但配置一样)
# ============================================
# 机器1
ssh user@192.168.0.11
java -jar /opt/xxl-job/xxl-job-executor-demo-1.0.0.jar \
    --xxl.job.executor.ip=192.168.0.11 \
    --xxl.job.executor.port=9999

# 机器2
ssh user@192.168.0.12
java -jar /opt/xxl-job/xxl-job-executor-demo-1.0.0.jar \
    --xxl.job.executor.ip=192.168.0.12 \
    --xxl.job.executor.port=9999

# 机器3
ssh user@192.168.0.13
java -jar /opt/xxl-job/xxl-job-executor-demo-1.0.0.jar \
    --xxl.job.executor.ip=192.168.0.13 \
    --xxl.job.executor.port=9999

验证集群

在 Admin 管理界面 执行器管理 中可以看到:

复制代码
┌─────────────────────────────────────────────────────────────────┐
│  执行器: 示例执行器 (xxl-job-executor-demo)                       │
│                                                                 │
│  OnLine 机器地址:                                                │
│  ├─ http://192.168.0.11:9999/    ← 自动注册                      │
│  ├─ http://192.168.0.12:9999/    ← 自动注册                      │
│  └─ http://192.168.0.13:9999/    ← 自动注册                      │
│                                                                 │
│  状态: 3台在线                                                   │
└─────────────────────────────────────────────────────────────────┘

路由策略验证

复制代码
选用路由策略 [轮询]:
  第1次调度 → 192.168.0.11:9999
  第2次调度 → 192.168.0.12:9999
  第3次调度 → 192.168.0.13:9999
  第4次调度 → 192.168.0.11:9999  (循环)

选用路由策略 [分片广播]:
  192.168.0.11:9999 → 处理数据 0,3,6,9...
  192.168.0.12:9999 → 处理数据 1,4,7,10...
  192.168.0.13:9999 → 处理数据 2,5,8,11...
实操18:调度中心集群 + Nginx 负载均衡
复制代码
┌─────────────────────────────────────────────────────────────────┐
│              调度中心集群 + Nginx 负载均衡                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                      ┌──────────────┐                           │
│                      │   用户/执行器  │                           │
│                      └──────┬───────┘                           │
│                             │                                   │
│                      ┌──────▼───────┐                           │
│                      │    Nginx     │  统一入口                   │
│                      │   :80/xxl-   │                           │
│                      │  job-admin   │                           │
│                      └──┬──────┬────┘                           │
│                         │      │                                │
│              ┌──────────▼┐  ┌──▼──────────┐                     │
│              │ Admin-1   │  │ Admin-2     │                     │
│              │ :8080     │  │ :8081       │                     │
│              └─────┬─────┘  └──────┬──────┘                     │
│                    │               │                            │
│                    └───────┬───────┘                            │
│                            │                                    │
│                   ┌────────▼────────┐                           │
│                   │   MySQL 数据库   │                           │
│                   │   (xxl_job 库)  │                           │
│                   │ 分布式锁:        │                           │
│                   │ xxl_job_lock表   │                           │
│                   └─────────────────┘                           │
│                                                                 │
│  高可用原理:                                                     │
│  - Nginx 实现负载均衡(用户/执行器入口)                            │
│  - MySQL 行锁 (SELECT ... FOR UPDATE) 保证只有一个 Admin 调度     │
│  - 任一台 Admin 宕机不影响整体,其他 Admin 自动接管                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Nginx 配置 (/etc/nginx/conf.d/xxl-job.conf):

nginx 复制代码
upstream xxl-job-admin {
    # Admin 集群节点
    server 192.168.0.1:8080  weight=1 max_fails=3 fail_timeout=30s;
    server 192.168.0.2:8080  weight=1 max_fails=3 fail_timeout=30s;
}

server {
    listen 80;
    server_name xxl-job.yourdomain.com;

    # 日志
    access_log /var/log/nginx/xxl-job-access.log;
    error_log  /var/log/nginx/xxl-job-error.log;

    location /xxl-job-admin {
        proxy_pass http://xxl-job-admin;

        # 代理头设置
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 超时设置
        proxy_connect_timeout 30s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
}

启动 Admin 集群

bash 复制代码
# 节点1
java -jar xxl-job-admin-2.5.0.jar --server.port=8080

# 节点2
java -jar xxl-job-admin-2.5.0.jar --server.port=8081

集群调度锁机制

sql 复制代码
-- XXL-JOB 使用 MySQL 行锁实现调度互斥
-- xxl_job_lock 表中只有一条记录:
SELECT * FROM xxl_job_lock;
-- +-----------------------+
-- | lock_name             |
-- +-----------------------+
-- | schedule_lock         |  ← 调度锁
-- +-----------------------+

-- 每次调度前,Admin 执行:
-- SELECT * FROM xxl_job_lock WHERE lock_name = 'schedule_lock' FOR UPDATE;
-- 获取到锁的 Admin 负责本轮调度,其他 Admin 等待
-- 释放后下一个 Admin 获取锁...

3.7 真实云环境部署验证

本节为 2026-06-03 在华为云 ecs-f5eb 集群上的真实部署验证结果,所有 IP、配置、截图均为真实数据。

实操19:云环境完整部署
部署拓扑
复制代码
┌─────────────────────────────────────────────────────────────────┐
│               XXL-JOB 真实云环境部署拓扑 (ecs-f5eb)                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌───────────────────────────────────────────────────────────┐ │
│  │         xxljob-01 (ecs-f5eb-0001)                         │ │
│  │         公网: 121.36.61.101  内网: 192.168.0.205           │ │
│  │         ┌──────────────────────────────┐                  │ │
│  │         │  XXL-JOB Admin :8080         │                  │ │
│  │         │  MySQL 8.0.46  :3306         │                  │ │
│  │         │  JDK 17.0.19 + Maven 3.8.7   │                  │ │
│  │         └──────────────────────────────┘                  │ │
│  └────────────────────────┬──────────────────────────────────┘ │
│                           │ HTTP (AccessToken: xxljob2024)     │
│          ┌────────────────┼────────────────┐                    │
│          │                │                │                    │
│  ┌───────▼────────┐ ┌────▼────────┐ ┌────▼────────┐           │
│  │  xxljob-02     │ │  xxljob-03  │ │  xxljob-04  │           │
│  │  Executor-1    │ │  Executor-2 │ │  Executor-3 │           │
│  │  公网:         │ │  公网:      │ │  公网:      │           │
│  │  120.46.133.64 │ │  1.94.216.49│ │  121.36.6.178│          │
│  │  :8081 (Web)   │ │  :8081      │ │  :8081      │           │
│  │  :9999 (RPC)   │ │  :9999      │ │  :9999      │           │
│  │  JDK 17+Maven  │ │  JDK 17+Maven│ │  JDK 17+Maven│          │
│  └────────────────┘ └─────────────┘ └─────────────┘           │
│                                                                 │
│  所有节点: Ubuntu 24.04, 2vCPU/4GiB, 按需计费                    │
│  执行器集群 AppName: xxl-job-executor-cluster                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
服务器规格
节点 公网IP 内网IP 角色 关键软件
xxljob-01 121.36.61.101 192.168.0.205 Admin + MySQL JDK 17.0.19, Maven 3.8.7, MySQL 8.0.46
xxljob-02 120.46.133.64 192.168.0.73 Executor-1 JDK 17.0.19, Maven 3.8.7
xxljob-03 1.94.216.49 192.168.0.179 Executor-2 JDK 17.0.19, Maven 3.8.7
xxljob-04 121.36.6.178 192.168.0.246 Executor-3 JDK 17.0.19, Maven 3.8.7
数据库初始化(xxljob-01 实际执行)
sql 复制代码
-- 1. 设置 root 密码
ALTER USER 'root'@'localhost' IDENTIFIED BY 'XxlJob@2024!';
FLUSH PRIVILEGES;

-- 2. 创建数据库
CREATE DATABASE IF NOT EXISTS xxl_job
    DEFAULT CHARACTER SET utf8mb4
    DEFAULT COLLATE utf8mb4_unicode_ci;

-- 3. 执行建表脚本
USE xxl_job;
SOURCE /opt/xxl-job/doc/db/tables_xxl_job.sql;

-- 4. 验证(9张表)
SHOW TABLES;
-- +------------------------+
-- | xxl_job_group          |
-- | xxl_job_info           |
-- | xxl_job_lock           |
-- | xxl_job_log            |
-- | xxl_job_log_report     |
-- | xxl_job_logglue        |
-- | xxl_job_registry       |
-- | xxl_job_user           |
-- | xxl_job_log_ext        |
-- +------------------------+
Admin 实际配置(xxljob-01)
properties 复制代码
# /opt/xxl-job/xxl-job-admin/src/main/resources/application.properties
server.port=8080
server.servlet.context-path=/xxl-job-admin

# 数据库(HikariCP 连接池)
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=XxlJob@2024!
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# 核心配置
xxl.job.accessToken=xxljob2024
xxl.job.i18n=zh_CN
xxl.job.triggerpool.fast.max=200
xxl.job.triggerpool.slow.max=100
xxl.job.logretentiondays=30
Executor 配置示例(xxljob-02, 其他节点 IP 不同)
properties 复制代码
# /opt/xxl-job-executor/src/main/resources/application.properties
server.port=8081

# 指向 Admin 公网地址
xxl.job.admin.addresses=http://121.36.61.101:8080/xxl-job-admin
xxl.job.accessToken=xxljob2024

# 执行器注册信息
xxl.job.executor.appname=xxl-job-executor-cluster
xxl.job.executor.ip=120.46.133.64
xxl.job.executor.port=9999
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
xxl.job.executor.logretentiondays=30
Executor 核心代码(3台执行器相同)

XxlJobConfig.java --- 执行器配置类:

java 复制代码
@Configuration
public class XxlJobConfig {
    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;
    @Value("${xxl.job.accessToken}")
    private String accessToken;
    @Value("${xxl.job.executor.appname}")
    private String appname;
    @Value("${xxl.job.executor.ip}")
    private String ip;
    @Value("${xxl.job.executor.port}")
    private int port;
    @Value("${xxl.job.executor.logpath}")
    private String logPath;
    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        XxlJobSpringExecutor e = new XxlJobSpringExecutor();
        e.setAdminAddresses(adminAddresses);
        e.setAccessToken(accessToken);
        e.setAppname(appname);
        e.setIp(ip);
        e.setPort(port);
        e.setLogPath(logPath);
        e.setLogRetentionDays(logRetentionDays);
        return e;
    }
}

已注册的 JobHandler

java 复制代码
// 1. 简单任务 --- 演示基本用法
@XxlJob("demoJobHandler")
public void demoJobHandler() {
    XxlJobHelper.log("XXL-JOB, Hello World.");
    System.out.println("demoJobHandler executed at " + new Date());
    XxlJobHelper.handleSuccess("执行成功");
}

// 2. 心跳任务 --- 演示定时心跳上报
@XxlJob("heartbeatJobHandler")
public void heartbeatJobHandler() {
    XxlJobHelper.log("心跳上报: " + new Date());
    XxlJobHelper.handleSuccess();
}

// 3. 参数任务 --- 演示任务参数传递
@XxlJob("paramJobHandler")
public void paramJobHandler() {
    String param = XxlJobHelper.getJobParam();
    XxlJobHelper.log("接收到参数: " + param);
    XxlJobHelper.handleSuccess();
}

// 4. 分片任务 --- 演示分片广播
@XxlJob("shardingJobHandler")
public void shardingJobHandler() {
    int shardIndex = XxlJobHelper.getShardIndex();
    int shardTotal = XxlJobHelper.getShardTotal();
    XxlJobHelper.log("分片 " + (shardIndex + 1) + "/" + shardTotal);
    XxlJobHelper.handleSuccess();
}
Maven 编译与启动
bash 复制代码
# ===========================================
# 所有节点统一使用 Maven 源码方式启动(开发/测试环境)
# ===========================================

# 1. 先编译(首次需下载依赖,约 3-5 分钟)
cd /opt/xxl-job/xxl-job-admin && mvn package -DskipTests
cd /opt/xxl-job-executor && mvn package -DskipTests

# 2. 用 spring-boot:run 启动(后端运行)
# Admin
cd /opt/xxl-job/xxl-job-admin
nohup mvn spring-boot:run > /data/logs/admin-run.log 2>&1 &

# Executor(3台各自执行)
cd /opt/xxl-job-executor
nohup mvn spring-boot:run > /data/logs/executor-run.log 2>&1 &

# 注意事项:
# - 首次启动 Maven 需下载 spring-boot-maven-plugin 等插件依赖
# - Admin 必须先于 Executor 启动
# - 使用 nohup 后台运行时务必确保 SSH 通道关闭不发送 SIGHUP

⚡ 踩坑提醒 : SSH get_pty=True 模式下,SSH 会话关闭会导致后台进程收到 SIGHUP 信号被杀。即使使用了 nohup,在 PTY 模式下也可能失效。解决方案:使用无 PTY 的 transport.open_session() 发送启动命令,或使用 at now / systemd service 方式启动。

Admin 启动成功日志
复制代码
Started XxlJobAdminApplication in 12.5 seconds (JVM running for 14.2)
HikariPool-1 - Starting...
HikariPool-1 - Start completed.
init xxl-job admin success.
执行器注册到 Admin
bash 复制代码
# 手动验证注册 API(curl 测试)
curl -s -X POST 'http://121.36.61.101:8080/xxl-job-admin/api/registry' \
  -H 'XXL-JOB-ACCESS-TOKEN: xxljob2024' \
  -H 'Content-Type: application/json' \
  -d '{"registryGroup":"EXECUTOR","registryKey":"xxl-job-executor-cluster","registryValue":"http://120.46.133.64:9999/"}'

# 返回: {"code":200,"msg":null,"content":null}
# ✅ code=200 表示注册成功
数据库注册记录验证
sql 复制代码
SELECT * FROM xxl_job.xxl_job_registry;

-- +----+-----------------------------+------------------------------+-----------------------------+
-- | id | registry_group              | registry_key                 | registry_value              |
-- +----+-----------------------------+------------------------------+-----------------------------+
-- |  1 | EXECUTOR                    | xxl-job-executor-cluster     | http://120.46.133.64:9999/  |
-- |  2 | EXECUTOR                    | xxl-job-executor-cluster     | http://1.94.216.49:9999/    |
-- |  3 | EXECUTOR                    | xxl-job-executor-cluster     | http://121.36.6.178:9999/   |
-- +----+-----------------------------+------------------------------+-----------------------------+
最终部署状态
节点 公网IP :8080 Admin :8081 Web :9999 Remoting 注册状态
xxljob-01 121.36.61.101 ✅ ONLINE --- --- Admin 主节点
xxljob-02 120.46.133.64 --- ✅ 已注册
xxljob-03 1.94.216.49 --- ✅ 已注册
xxljob-04 121.36.6.178 --- ✅ 已注册
访问信息
项目
Admin 管理台 http://121.36.61.101:8080/xxl-job-admin
登录账号 admin / 123456
Access Token xxljob2024
数据库 MySQL xxl_job(root / XxlJob@2024!
执行器集群 AppName=xxl-job-executor-cluster,3 节点

四、高级应用篇

4.1 性能优化

数据库连接池优化
properties 复制代码
# ============================================
# HikariCP 连接池调优(xxl-job-admin)
# ============================================

# 最大连接数(根据并发任务量调整)
# 公式: 最大连接数 ≈ 并发任务数 × 每个任务可能的数据库操作
spring.datasource.hikari.maximum-pool-size=50

# 最小空闲连接数
spring.datasource.hikari.minimum-idle=10

# 连接超时(毫秒)
spring.datasource.hikari.connection-timeout=30000

# 空闲超时(毫秒)
spring.datasource.hikari.idle-timeout=600000

# 最大生命周期(毫秒,应小于 MySQL wait_timeout)
spring.datasource.hikari.max-lifetime=1800000

# 连接测试查询
spring.datasource.hikari.connection-test-query=SELECT 1
调度线程池优化
properties 复制代码
# ============================================
# 调度线程池配置
# ============================================

# 快任务线程池(Cron 触发的调度请求)
xxl.job.triggerpool.fast.max=200

# 慢任务线程池(手动触发、子任务触发、失败重试)
xxl.job.triggerpool.slow.max=100

线程池设计原理

复制代码
                    ┌─────────────────┐
                    │  调度线程入口     │
                    └────────┬────────┘
                             │
              ┌──────────────┴──────────────┐
              │                             │
     ┌────────▼────────┐           ┌────────▼────────┐
     │  快任务线程池     │           │  慢任务线程池     │
     │  (fast.max=200) │           │  (slow.max=100) │
     │                 │           │                 │
     │ 处理: Cron触发   │           │ 处理: 手动触发    │
     │      定时调度    │           │       子任务     │
     │                 │           │       失败重试   │
     └─────────────────┘           └─────────────────┘
执行器性能优化
java 复制代码
/**
 * 高性能执行器任务模板
 */
@Component
public class HighPerfJob {

    // 使用线程池进行业务处理,避免阻塞 XXL-JOB 回调线程
    private final ExecutorService bizExecutor = new ThreadPoolExecutor(
        4,                              // corePoolSize
        8,                              // maximumPoolSize
        60L, TimeUnit.SECONDS,          // keepAliveTime
        new LinkedBlockingQueue<>(1000), // workQueue
        new ThreadFactoryBuilder().setNameFormat("biz-pool-%d").build(),
        new ThreadPoolExecutor.CallerRunsPolicy()
    );

    @XxlJob("highPerfJobHandler")
    public void highPerfJobHandler() throws Exception {
        long startTime = System.currentTimeMillis();

        // 分批处理 + 并行执行
        int batchSize = 500;
        List<CompletableFuture<Void>> futures = new ArrayList<>();

        for (int offset = 0; offset < 10000; offset += batchSize) {
            final int start = offset;
            final int end = Math.min(offset + batchSize, 10000);

            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                processBatch(start, end);
            }, bizExecutor);

            futures.add(future);
        }

        // 等待所有批次完成
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

        long elapsed = System.currentTimeMillis() - startTime;
        XxlJobHelper.log("任务完成, 总耗时: {}ms", elapsed);
        XxlJobHelper.handleSuccess();
    }

    private void processBatch(int start, int end) {
        // 批量处理逻辑
        XxlJobHelper.log("处理批次: {} - {}", start, end);
    }
}
日志与数据库清理
sql 复制代码
-- ============================================
-- 定时清理历史日志(建议作为 XXL-JOB 任务本身)
-- ============================================

-- 删除 30 天前的调度日志
DELETE FROM xxl_job_log
WHERE trigger_time < DATE_SUB(NOW(), INTERVAL 30 DAY);

-- 清理孤儿日志报表
DELETE FROM xxl_job_log_report
WHERE trigger_day < DATE_SUB(CURDATE(), INTERVAL 30 DAY);

-- 优化表(回收空间)
OPTIMIZE TABLE xxl_job_log;

日志清理任务

java 复制代码
@XxlJob("cleanLogJobHandler")
public void cleanLogJobHandler() {
    // 此任务本身也可以配置在 XXL-JOB 中定期执行
    int deletedCount = xxlJobLogMapper.deleteBeforeDate(
        LocalDate.now().minusDays(30));
    XxlJobHelper.log("清理了 {} 条历史日志", deletedCount);
    XxlJobHelper.handleSuccess();
}

4.2 安全配置

AccessToken 认证
properties 复制代码
# ============================================
# 调度中心配置
# ============================================
xxl.job.accessToken=MySecretToken2024!@#

# ============================================
# 执行器配置(必须与调度中心一致)
# ============================================
xxl.job.accessToken=MySecretToken2024!@#

AccessToken 工作原理

复制代码
  执行器请求调度中心时:
    URL: http://admin:8080/xxl-job-admin/api/callback
    Header: XXL-JOB-ACCESS-TOKEN: MySecretToken2024!@#

  调度中心收到请求:
    ① 从 Header 中提取 XXL-JOB-ACCESS-TOKEN
    ② 与本地配置的 xxl.job.accessToken 比对
    ③ 一致 → 放行 / 不一致 → 拒绝并告警
用户权限管理
复制代码
┌─────────────────────────────────────────────────────────────────┐
│  XXL-JOB 用户角色权限                                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  角色        权限范围                                             │
│  ─────────────────────────────────────────────────────────────  │
│  管理员      全部权限: 任务CRUD、执行器管理、用户管理、系统配置      │
│  普通用户    受限权限: 任务CRUD(只能操作自己创建的)、查看日志       │
│                                                                 │
│  配置建议:                                                       │
│  - 生产环境: 修改默认 admin 密码(默认 123456)                     │
│  - 多人协作: 给每个开发人员创建独立账号                             │
│  - 运维人员: 创建只读账号,只能查看不能修改                         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

4.3 监控告警

邮件告警配置

在前面的 application.properties 中已配置邮件,在任务配置中填写 报警邮件 即可:

复制代码
任务管理 → 编辑 → 报警邮件: admin@company.com, dev@company.com

告警触发条件

场景 是否触发告警
任务执行失败
失败重试后仍失败
任务执行超时
任务执行成功
调度失败(执行器不可达)
钉钉机器人告警

XXL-JOB 原生不支持钉钉告警,但可以通过以下方式实现:

方案一:在任务中集成钉钉告警

java 复制代码
@Component
public class DingTalkAlertJob {

    /**
     * 钉钉告警工具任务示例
     */
    @XxlJob("dingTalkAlertDemo")
    public void dingTalkAlertDemo() {
        try {
            // 业务逻辑
            doSomething();

            XxlJobHelper.handleSuccess();
        } catch (Exception e) {
            // 失败时发送钉钉告警
            sendDingTalkAlert("任务执行失败", e.getMessage(), getUserPhones());
            throw e;
        }
    }

    /**
     * 发送钉钉告警消息
     */
    private void sendDingTalkAlert(String title, String content, List<String> atMobiles) {
        String webhookUrl = "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN";

        Map<String, Object> message = new LinkedHashMap<>();
        message.put("msgtype", "markdown");

        Map<String, String> markdown = new LinkedHashMap<>();
        markdown.put("title", title);

        StringBuilder sb = new StringBuilder();
        sb.append("## ⚠️ XXL-JOB 任务告警\n\n");
        sb.append("**告警标题**: ").append(title).append("\n\n");
        sb.append("**告警内容**: ").append(content).append("\n\n");
        sb.append("**告警时间**: ").append(LocalDateTime.now()).append("\n\n");
        sb.append("**执行器**: ").append(XxlJobHelper.getJobParam()).append("\n");

        // @相关人员
        if (atMobiles != null) {
            for (String mobile : atMobiles) {
                sb.append("@").append(mobile).append(" ");
            }
        }

        markdown.put("text", sb.toString());
        message.put("markdown", markdown);

        // 通过 HTTP POST 发送
        // RestTemplate/HttpClient 发送...
    }

    private List<String> getUserPhones() {
        return Arrays.asList("13800138000");
    }

    private void doSomething() { /* 业务逻辑 */ }
}

方案二:在调度中心源码中扩展

复制代码
修改 xxl-job-admin 的 JobAlarm 接口实现:
  1. 找到 EmailJobAlarm 实现类
  2. 新增 DingTalkJobAlarm 实现类
  3. 在 JobAlarmer 中注册

五、项目实战案例

5.1 数据同步任务(MySQL → Elasticsearch)

java 复制代码
@Component
public class DataSyncJob {

    // 模拟 Mapper
    // @Autowired private UserMapper userMapper;
    // @Autowired private RestHighLevelClient esClient;

    /**
     * MySQL 到 Elasticsearch 增量同步任务
     *
     * 部署建议:
     *   - Cron: 0 0/5 * * * ?  (每5分钟)
     *   - 路由策略: 轮询
     *   - 阻塞策略: 单机串行
     */
    @XxlJob("mysqlToEsSyncJob")
    public void mysqlToEsSyncJob() throws Exception {
        long startTime = System.currentTimeMillis();

        XxlJobHelper.log("【MySQL→ES同步】开始");

        // ① 从检查点表获取上次同步时间
        LocalDateTime lastSyncTime = getLastSyncTime();
        XxlJobHelper.log("上次同步时间: {}", lastSyncTime);

        // ② 查询增量数据(自上次同步后更新的数据)
        List<User> users = queryUpdatedUsers(lastSyncTime);

        if (users.isEmpty()) {
            XxlJobHelper.log("【MySQL→ES同步】无增量数据,跳过");
            XxlJobHelper.handleSuccess();
            return;
        }

        XxlJobHelper.log("获取到 {} 条增量数据", users.size());

        // ③ 批量写入 ES(使用 Bulk API)
        int batchSize = 200;
        int successCount = 0;
        int failCount = 0;
        LocalDateTime currentSyncTime = LocalDateTime.now();

        for (int i = 0; i < users.size(); i += batchSize) {
            int end = Math.min(i + batchSize, users.size());
            List<User> batch = users.subList(i, end);

            try {
                bulkIndexToEs(batch);
                successCount += batch.size();
                XxlJobHelper.log("批次 {}/{} 同步成功", i / batchSize + 1,
                    (users.size() + batchSize - 1) / batchSize);
            } catch (Exception e) {
                failCount += batch.size();
                XxlJobHelper.log("批次 {}/{} 同步失败: {}",
                    i / batchSize + 1, (users.size() + batchSize - 1) / batchSize,
                    e.getMessage());
            }
        }

        // ④ 更新同步检查点
        if (failCount == 0) {
            updateLastSyncTime(currentSyncTime);
        }

        long elapsed = System.currentTimeMillis() - startTime;
        String result = String.format(
            "【MySQL→ES同步】完成: 总数据=%d, 成功=%d, 失败=%d, 耗时=%dms",
            users.size(), successCount, failCount, elapsed
        );

        XxlJobHelper.log(result);

        if (failCount > 0) {
            XxlJobHelper.handleFail(result);
            throw new RuntimeException("部分数据同步失败");
        } else {
            XxlJobHelper.handleSuccess(result);
        }
    }

    // ======== 模拟方法 ========

    private LocalDateTime getLastSyncTime() {
        // 从 xxl_job_sync_checkpoint 表读取
        return LocalDateTime.now().minusHours(1);
    }

    private List<User> queryUpdatedUsers(LocalDateTime after) {
        // SELECT * FROM t_user WHERE update_time > #{after}
        // 模拟返回数据
        List<User> list = new ArrayList<>();
        for (int i = 0; i < 500; i++) {
            list.add(new User(i, "user_" + i, "user" + i + "@example.com"));
        }
        return list;
    }

    private void bulkIndexToEs(List<User> users) {
        // ES Bulk API 批量索引
    }

    private void updateLastSyncTime(LocalDateTime time) {
        // UPDATE xxl_job_sync_checkpoint SET last_sync_time = #{time}
    }

    static class User {
        long id;
        String name;
        String email;
        User(long id, String name, String email) {
            this.id = id; this.name = name; this.email = email;
        }
    }
}

5.2 报表生成系统

java 复制代码
@Component
public class ReportJob {

    /**
     * 日报生成任务
     *
     * Cron: 0 0 6 * * ? (每天早上6点)
     */
    @XxlJob("dailyReportJob")
    public void dailyReportJob() throws Exception {
        XxlJobHelper.log("【日报生成】开始");

        String yesterday = LocalDate.now().minusDays(1)
                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));

        // ① 收集数据
        DailyReportData data = new DailyReportData();
        data.date = yesterday;
        data.newUserCount = getNewUserCount(yesterday);
        data.orderCount = getOrderCount(yesterday);
        data.totalAmount = getTotalAmount(yesterday);
        data.activeUserCount = getActiveUserCount(yesterday);

        // ② 生成报告(HTML 格式)
        String reportHtml = generateReportHtml(data);

        // ③ 发送邮件
        sendEmailReport(reportHtml, yesterday);

        XxlJobHelper.log("【日报生成】完成: {}", yesterday);
        XxlJobHelper.handleSuccess();
    }

    /**
     * 周报生成任务
     *
     * Cron: 0 0 9 ? * 1 (每周一早上9点)
     */
    @XxlJob("weeklyReportJob")
    public void weeklyReportJob() throws Exception {
        XxlJobHelper.log("【周报生成】开始");

        LocalDate endDate = LocalDate.now().minusDays(1);
        LocalDate startDate = endDate.minusDays(6);

        WeeklyReportData data = aggregateWeeklyData(startDate, endDate);
        String report = generateWeeklyReportHtml(data);
        sendEmailReport(report, startDate + " ~ " + endDate);

        XxlJobHelper.log("【周报生成】完成");
        XxlJobHelper.handleSuccess();
    }

    /**
     * 月报生成任务
     *
     * Cron: 0 0 8 1 * ? (每月1号早上8点)
     */
    @XxlJob("monthlyReportJob")
    public void monthlyReportJob() throws Exception {
        XxlJobHelper.log("【月报生成】开始");

        YearMonth lastMonth = YearMonth.now().minusMonths(1);
        MonthlyReportData data = aggregateMonthlyData(lastMonth);
        String report = generateMonthlyReportHtml(data);
        sendEmailReport(report, lastMonth.toString());

        XxlJobHelper.log("【月报生成】完成");
        XxlJobHelper.handleSuccess();
    }

    // ======== 模拟方法和数据类 ========

    private String generateReportHtml(DailyReportData data) {
        return "<html><body><h1>日报 " + data.date + "</h1>"
            + "<p>新增用户: " + data.newUserCount + "</p>"
            + "<p>订单数: " + data.orderCount + "</p>"
            + "<p>交易额: " + data.totalAmount + "</p>"
            + "</body></html>";
    }

    private String generateWeeklyReportHtml(WeeklyReportData data) {
        return "<html><body><h1>周报</h1></body></html>";
    }

    private String generateMonthlyReportHtml(MonthlyReportData data) {
        return "<html><body><h1>月报</h1></body></html>";
    }

    private void sendEmailReport(String html, String period) {
        // JavaMailSender 发送邮件
        XxlJobHelper.log("报告邮件已发送, 周期: {}", period);
    }

    private WeeklyReportData aggregateWeeklyData(LocalDate start, LocalDate end) {
        return new WeeklyReportData();
    }

    private MonthlyReportData aggregateMonthlyData(YearMonth month) {
        return new MonthlyReportData();
    }

    // 模拟数据查询
    private int getNewUserCount(String date) { return 128; }
    private int getOrderCount(String date) { return 356; }
    private double getTotalAmount(String date) { return 45678.90; }
    private int getActiveUserCount(String date) { return 1024; }

    static class DailyReportData {
        String date;
        int newUserCount;
        int orderCount;
        double totalAmount;
        int activeUserCount;
    }
    static class WeeklyReportData {}
    static class MonthlyReportData {}
}

5.3 缓存预热任务

java 复制代码
@Component
public class CacheWarmJob {

    // @Autowired private RedisTemplate<String, Object> redisTemplate;
    // @Autowired private ProductMapper productMapper;

    /**
     * Redis 缓存预热任务
     *
     * 使用场景:
     *   - 系统重启后自动加载热点数据到缓存
     *   - 定时刷新缓存,保证数据一致性
     *
     * Cron: 0 0 3 * * ? (每天凌晨3点执行)
     */
    @XxlJob("cacheWarmJob")
    public void cacheWarmJob() throws Exception {
        long startTime = System.currentTimeMillis();

        XxlJobHelper.log("【缓存预热】开始");

        // 支持分片广播:每个执行器预热一部分数据
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();

        if (shardTotal == 1) {
            // 单机执行:预热全部
            warmUpAll();
        } else {
            // 分片执行:各预热一部分
            XxlJobHelper.log("分片预热: {}/{}", shardIndex + 1, shardTotal);
            warmUpByShard(shardIndex, shardTotal);
        }

        long elapsed = System.currentTimeMillis() - startTime;
        XxlJobHelper.log("【缓存预热】完成, 耗时: {}ms", elapsed);
        XxlJobHelper.handleSuccess();
    }

    /**
     * 全量预热
     */
    private void warmUpAll() {
        XxlJobHelper.log(">>> 预热商品分类缓存");
        // List<Category> categories = categoryMapper.selectAll();
        // categories.forEach(c -> redisTemplate.opsForValue()
        //     .set("cache:category:" + c.getId(), c, 1, TimeUnit.HOURS));

        XxlJobHelper.log(">>> 预热热门商品缓存");
        // List<Product> hotProducts = productMapper.selectHot(100);
        // hotProducts.forEach(p -> redisTemplate.opsForValue()
        //     .set("cache:product:" + p.getId(), p, 30, TimeUnit.MINUTES));

        XxlJobHelper.log(">>> 预热配置缓存");
        // List<Config> configs = configMapper.selectAll();
        // configs.forEach(c -> redisTemplate.opsForValue()
        //     .set("cache:config:" + c.getKey(), c.getValue()));

        XxlJobHelper.log(">>> 缓存预热全部完成");
    }

    /**
     * 分片预热(配合路由策略"分片广播"使用)
     */
    private void warmUpByShard(int shardIndex, int shardTotal) {
        // 例如: 预热商品数据,按商品ID分片
        int totalProducts = 10000;
        int processedCount = 0;

        for (int id = 1; id <= totalProducts; id++) {
            if (id % shardTotal == shardIndex) {
                // Product p = productMapper.selectById(id);
                // redisTemplate.opsForValue()
                //     .set("cache:product:" + id, p, 30, TimeUnit.MINUTES);
                processedCount++;
            }
        }

        XxlJobHelper.log("分片 {}/{} 处理了 {} 条数据", shardIndex + 1, shardTotal, processedCount);
    }
}

5.4 批量数据处理(分片广播 + 断点续传)

java 复制代码
@Component
public class BatchProcessJob {

    /**
     * 大规模数据处理任务
     *
     * 配置要求:
     *   - 路由策略: 分片广播
     *   - Cron: 0 0 2 * * ? (每天凌晨2点)
     *   - 阻塞处理策略: 单机串行
     *
     * 适用场景:
     *   - 百万级数据清洗/迁移
     *   - 大文件批量解析
     */
    @XxlJob("batchProcessJob")
    public void batchProcessJob() throws Exception {
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();

        XxlJobHelper.log("【批量处理】启动: 分片 {}/{}", shardIndex + 1, shardTotal);

        // ① 获取待处理数据总数
        int totalCount = getPendingDataCount();
        XxlJobHelper.log("待处理总数: {}", totalCount);

        // ② 分片计算本执行器负责的数据范围
        int batchSize = 200;

        int successCount = 0;
        int failCount = 0;
        int skipCount = 0;

        for (int offset = shardIndex * batchSize; offset < totalCount; offset += shardTotal * batchSize) {
            int limit = Math.min(batchSize, totalCount - offset);

            XxlJobHelper.log("处理批次: offset={}, limit={}", offset, limit);

            try {
                // ③ 查询本批次数据
                List<DataRecord> batch = fetchBatch(offset, limit);

                for (DataRecord record : batch) {
                    try {
                        if (isProcessed(record.getId())) {
                            skipCount++;  // 已处理,跳过(断点续传)
                            continue;
                        }

                        processRecord(record);
                        markProcessed(record.getId());
                        successCount++;

                    } catch (Exception e) {
                        failCount++;
                        XxlJobHelper.log("记录 {} 处理失败: {}", record.getId(), e.getMessage());
                    }
                }
            } catch (Exception e) {
                XxlJobHelper.log("批次查询失败 (offset={}): {}", offset, e.getMessage());
            }
        }

        // ④ 生成结果报告
        String result = String.format(
            "【批量处理】完成: 分片=%d/%d, 成功=%d, 失败=%d, 跳过=%d",
            shardIndex + 1, shardTotal, successCount, failCount, skipCount
        );

        XxlJobHelper.log(result);

        if (failCount > 0) {
            XxlJobHelper.handleFail(result);
        } else {
            XxlJobHelper.handleSuccess(result);
        }
    }

    // ===== 模拟数据访问方法 =====
    private int getPendingDataCount() { return 10000; }

    private List<DataRecord> fetchBatch(int offset, int limit) {
        List<DataRecord> batch = new ArrayList<>();
        for (int i = 0; i < limit; i++) {
            batch.add(new DataRecord(offset + i, "data_" + (offset + i)));
        }
        return batch;
    }

    private boolean isProcessed(long id) { return false; }

    private void processRecord(DataRecord record) {
        // 实际业务处理逻辑
    }

    private void markProcessed(long id) {
        // INSERT INTO batch_process_record (record_id, status, process_time)
        // VALUES (id, 'SUCCESS', NOW())
    }

    static class DataRecord {
        long id;
        String data;
        DataRecord(long id, String data) { this.id = id; this.data = data; }
        public long getId() { return id; }
    }
}

六、常见问题与解决方案

6.1 连接问题

问题1:执行器无法注册到调度中心
复制代码
┌─────────────────────────────────────────────────────────────────┐
│  现象: Admin "执行器管理"中看不到执行器 Online                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  排查步骤:                                                       │
│                                                                 │
│  ① 检查执行器启动日志                                              │
│     >>>>>>>>>>> xxl-job, job executor start success.             │
│     >>>>>>>>>>> xxl-job executor registry success,               │
│                 registryKey:xxl-job-executor-demo                │
│     如果没有 registry success 日志 → 第②步                         │
│                                                                 │
│  ② 检查网络连通性                                                 │
│     在「执行器」所在机器执行:                                       │
│     curl http://调度中心IP:8080/xxl-job-admin/                    │
│     确认网络可达                                                  │
│                                                                 │
│  ③ 检查 AppName 一致性                                           │
│     执行器 application.yml: xxl.job.executor.appname              │
│     Admin 执行器管理: AppName                                     │
│     ★ 两者必须完全一致(包括大小写)!                               │
│                                                                 │
│  ④ 检查 AccessToken                                              │
│     如果 Admin 配置了 accessToken,执行器也必须配置相同的            │
│                                                                 │
│  ⑤ 检查防火墙/安全组                                              │
│     执行器端口 (默认 9999) 必须在安全组中开放                        │
│                                                                 │
│  ⑥ 检查多网卡环境                                                │
│     如果服务器有多个网卡,手动指定 ip:                              │
│     xxl.job.executor.ip=内网IP                                   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
问题2:调度中心连接超时
java 复制代码
// 执行器端增加超时配置
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
    XxlJobSpringExecutor executor = new XxlJobSpringExecutor();
    // ... 其他配置 ...

    // 心跳间隔(默认30秒,可适当缩小用于测试)
    // 注意:2.5.0 版本在 XxlJobSpringExecutor 中已内置

    return executor;
}

6.2 任务执行问题

问题3:任务不执行
可能原因 检查方法 解决方案
任务未启动 Admin 上任务状态显示"STOP" 点击"启动"按钮
Cron 表达式错误 使用在线工具验证 修正 Cron 表达式
执行器离线 Admin 执行器管理查看状态 重启执行器
JobHandler 名称不匹配 对比代码 @XxlJob("xxx") 和 Admin 配置 保持一致
路由策略问题 检查执行器数量是否匹配策略 选择合适策略
阻塞策略拦截 上次任务未完成 选择"覆盖之前调度"
问题4:任务重复执行
复制代码
原因分析:
  ① 调度中心集群的分布式锁失效(MySQL 锁超时)
     → 检查 MySQL 连接是否稳定
     → 检查是否有长时间未提交的事务

  ② 配置了多个完全相同的任务
     → 在 Admin 任务管理中逐一核对

  ③ 路由策略为"分片广播"(这是正常行为)
     → 检查是否为误解
问题5:任务卡死
java 复制代码
/**
 * 带超时控制的任务
 */
@XxlJob("timeoutControlJob")
public void timeoutControlJob() throws Exception {
    // 方式1: 在 Admin 上配置 "任务超时时间"(秒),超时后自动中断

    // 方式2: 代码手动控制超时
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Future<?> future = executor.submit(() -> {
        // 可能卡死的业务逻辑
        potentiallyBlockingOperation();
    });

    try {
        // 30分钟超时
        future.get(30, TimeUnit.MINUTES);
        XxlJobHelper.handleSuccess();
    } catch (TimeoutException e) {
        future.cancel(true);  // 中断执行
        XxlJobHelper.handleFail("任务执行超时,已中断");
        throw e;
    } finally {
        executor.shutdownNow();
    }
}

private void potentiallyBlockingOperation() {
    // 实际业务逻辑
}

6.3 性能问题

问题6:任务堆积
复制代码
┌─────────────────────────────────────────────────────────────────┐
│  任务堆积排查流程                                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ① 检查 Admin 调度日志                                          │
│     → 查看"调度结果"和"执行结果"                                  │
│     → 调度成功但执行耗时过长 → 业务逻辑问题                        │
│     → 调度失败 → 网络/执行器问题                                  │
│                                                                 │
│  ② 检查线程池状态                                                │
│     → Admin: triggerpool.fast.max / triggerpool.slow.max        │
│     → Executor: 执行器自身线程池                                 │
│                                                                 │
│  ③ 解决方案                                                     │
│     ├─ 增加执行器实例(水平扩展)                                  │
│     ├─ 增大线程池配置                                            │
│     ├─ 使用分片广播并行处理                                       │
│     ├─ 优化业务逻辑(减少 DB 查询、使用批量操作)                   │
│     └─ 调整 Cron 频率(降低执行频率)                              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
问题7:数据库压力大
sql 复制代码
-- ============================================
-- 数据库优化措施
-- ============================================

-- ① 为高频查询字段创建索引
ALTER TABLE xxl_job_log ADD INDEX idx_trigger_time (trigger_time);
ALTER TABLE xxl_job_log ADD INDEX idx_job_id (job_id);
ALTER TABLE xxl_job_log ADD INDEX idx_handle_code (handle_code);

-- ② 定期清理历史日志
-- 配置为 XXL-JOB 定时任务,每天凌晨清理
DELETE FROM xxl_job_log
WHERE trigger_time < DATE_SUB(NOW(), INTERVAL 7 DAY)
LIMIT 10000;  -- 分批删除,避免长事务

-- ③ 读写分离(如果数据量大)
-- 为调度日志配置独立的从库读取
问题8:内存溢出(OOM)
bash 复制代码
# JVM 参数调优(执行器启动时)
java -Xms512m -Xmx2048m \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/data/logs/xxl-job/ \
     -jar xxl-job-executor-demo-1.0.0.jar

# 参数说明:
# -Xms512m         初始堆大小
# -Xmx2048m        最大堆大小(根据实际调整)
# -XX:+UseG1GC     使用 G1 垃圾回收器
# +HeapDump...     发生 OOM 时自动生成 dump 文件

6.4 真实部署踩坑记录

以下 6 个坑来自 2026-06-03 华为云 ecs-f5eb 集群的真实部署过程,耗时约 45 分钟逐一解决。

坑1:apt lock 冲突(权重 ⭐⭐⭐)
复制代码
┌─────────────────────────────────────────────────────────────────┐
│  现象: apt-get install 报错 "Could not get lock /var/lib/        │
│        dpkg/lock-frontend"                                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  根因: 新重装的 Ubuntu 24.04 系统,后台有 unattended-upgrades    │
│        进程正在自动更新,锁住了 dpkg。                              │
│                                                                 │
│  解决:                                                          │
│  # 杀掉所有 apt/dpkg 进程                                        │
│  pkill -9 -f apt                                                │
│  pkill -9 -f dpkg                                               │
│                                                                 │
│  # 清理锁文件                                                    │
│  rm -f /var/lib/dpkg/lock-frontend                              │
│  rm -f /var/lib/dpkg/lock                                       │
│  rm -f /var/cache/apt/archives/lock                             │
│                                                                 │
│  # 重新配置 dpkg                                                 │
│  dpkg --configure -a                                            │
│                                                                 │
│  教训: 新装系统后第一步就检查 `lsof /var/lib/dpkg/lock-frontend` │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
坑2:Bash ${} 变量展开破坏 Java 注解(权重 ⭐⭐⭐⭐⭐)
复制代码
┌─────────────────────────────────────────────────────────────────┐
│  现象: 通过 SSH heredoc 写入 Java 文件后,@Value("${xxl.job.xxx}")│
│        中的 ${} 被 Bash 解析为空字符串,注解失效。                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  根因: Bash 的 heredoc(即使单引号 EOF)在某些复杂场景下仍会尝试  │
│        解析 ${} 表达式;SSH 远程执行时多层引号嵌套加剧问题。        │
│                                                                 │
│  错误示例:                                                      │
│  ssh root@host 'cat > file.java << '"'"'EOF'"'"'               │
│  @Value("${xxl.job.admin.addresses}")  ← ${} 被展开              │
│  EOF'                                                           │
│                                                                 │
│  解决: 使用 Python heredoc + \\\\x24 转义 $                      │
│  ssh root@host 'python3 << '"'"'PYEOF'"'"'                     │
│  content = """                                                  │
│  @Value("\\\\x24{xxl.job.admin.addresses}")                     │
│  private String adminAddresses;                                 │
│  """                                                            │
│  with open("/path/to/file.java", "w") as f:                     │
│      f.write(content)                                           │
│  PYEOF'                                                         │
│                                                                 │
│  原理: \\\\x24 是 $ 的十六进制编码,Python 写入文件时自动转换。     │
│                                                                 │
│  教训: 通过 SSH 写入包含 ${} 的 Java/Kotlin 文件时,首选 Python   │
│        heredoc 方式,永远不要用 cat << 'EOF'。                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
坑3:SSH nohup + PTY 导致 SIGHUP 杀进程(权重 ⭐⭐⭐⭐⭐)
复制代码
┌─────────────────────────────────────────────────────────────────┐
│  现象: 通过 paramiko `get_pty=True` 发送 nohup 后台命令后,SSH  │
│        通道关闭,后台 Java 进程被 SIGHUP 信号杀掉。                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  根因: 当 SSH 通道关联了 PTY(伪终端),通道关闭时内核会向 PTY   │
│        关联的所有进程发送 SIGHUP。即使使用了 `nohup`,也无效。     │
│                                                                 │
│  失败尝试:                                                      │
│  ❌ nohup java -jar ... &        → SIGHUP 杀进程                │
│  ❌ nohup java -jar ... & disown → SIGHUP 杀进程                │
│  ❌ setsid nohup java -jar ... & → SIGHUP 杀进程                │
│  ❌ screen -dmS xxx bash -c '...' → 语法转义问题                 │
│                                                                 │
│  解决: paramiko 使用 transport.open_session() 代替 exec_command  │
│  def ssh_send_bg(host, cmd):                                    │
│      c = paramiko.SSHClient()                                   │
│      c.connect(hostname=host, username='root', password=pwd)    │
│      transport = c.get_transport()                              │
│      channel = transport.open_session()    # 无 PTY!             │
│      channel.exec_command(cmd)                                  │
│      c.close()                                                  │
│                                                                 │
│  原理: open_session() 默认不分配 PTY,通道关闭时不发送 SIGHUP。    │
│                                                                 │
│  生产环境推荐: 使用 systemd service 管理 Java 进程,彻底避免此问题。│
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
坑4:accessToken 不一致导致注册失败(权重 ⭐⭐⭐⭐)
复制代码
┌─────────────────────────────────────────────────────────────────┐
│  现象: Executor 日志显示 "The access token is wrong.",持续注册   │
│        失败,Admin 端看不到任何执行器在线。                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  根因: Admin 源码默认 accessToken=default_token,但 Executor     │
│        配置文件写的是 xxljob2024。两者不匹配。                     │
│                                                                 │
│  Admin 配置 (修改前):                                           │
│  xxl.job.accessToken=default_token     ← 默认值                  │
│                                                                 │
│  Executor 配置:                                                  │
│  xxl.job.accessToken=xxljob2024        ← 自定义值                │
│                                                                 │
│  解决: 统一两边为相同值                                          │
│  # Admin 端                                                     │
│  sed -i 's/accessToken=default_token/accessToken=xxljob2024/'   \
│      application.properties                                     │
│                                                                 │
│  教训: 部署前务必检查 Admin 和所有 Executor 的 accessToken       │
│        一致性;生产环境建议使用统一配置中心管理。                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
坑5:启动时序问题(权重 ⭐⭐⭐)
复制代码
┌─────────────────────────────────────────────────────────────────┐
│  现象: Executor 先于 Admin 启动,注册线程反复报 "Connection       │
│        refused",直到重试耗尽后停止注册。                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  根因: Executor 启动时 Admin 尚未运行(Admin 因 Maven 依赖多,   │
│        编译/启动时间比 Executor 长约 2-3 分钟)。                  │
│                                                                 │
│  时间线:                                                        │
│  22:50  Executor-02/03/04 开始 spring-boot:run                  │
│  22:55  Executor 启动完成,开始向 Admin 注册                      │
│  22:55  Connection refused → Admin 还没起来!                     │
│  22:55  Executor 每 30 秒重试,连续失败                           │
│  23:08  Admin 终于启动完成                                        │
│  23:08  Executor 注册线程已停止(重试次数耗尽)                    │
│                                                                 │
│  解决: 先确保 Admin 完全启动后,再重启所有 Executor。             │
│                                                                 │
│  教训: 部署顺序很重要!                                           │
│  ① MySQL → ② Admin → ③ Executor                                │
│  并在 Admin 完全启动(端口监听 + HTTP 200)后再启动 Executor。     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
坑6:Maven spring-boot:run 日志缓冲不实时(权重 ⭐⭐)
复制代码
┌─────────────────────────────────────────────────────────────────┐
│  现象: 使用 nohup mvn spring-boot:run > logfile 2>&1 & 启动后,  │
│        日志文件内容严重滞后,注册状态无法及时观察到。               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  根因: Maven 和 Spring Boot 使用多层缓冲(JVM 缓冲 → Maven 缓冲  │
│        → Shell 管道缓冲),nohup 重定向进一步加剧延迟。            │
│                                                                 │
│  日志实际状态 vs 文件显示状态:                                    │
│  23:10  进程启动完成,端口已监听                                    │
│  23:11  日志文件只有 3 行(Maven 启动信息)                        │
│  23:14  日志文件多了 15 行                                        │
│  23:15  注册成功的日志才完全刷出                                    │
│                                                                 │
│  影响: 无法通过实时监控日志判断服务状态,但**不影响服务正常运行**。  │
│                                                                 │
│  解决方案:                                                      │
│  ① 用 ss -tlnp | grep <port> 直接检查端口(最可靠)              │
│  ② 用 curl 测试 HTTP 接口                                       │
│  ③ 数据库查询 xxl_job_registry 表验证注册状态                     │
│  ④ 生产环境使用 JAR + java -jar 方式启动(日志更及时)            │
│                                                                 │
│  教训: 排查部署问题时,端口/进程检查 > 日志文件检查。              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

6大踩坑总结

# 坑点 严重度 根因分类 关键词
1 apt lock 冲突 ⭐⭐⭐ 系统环境 unattended-upgrades
2 Bash ${} 转义 ⭐⭐⭐⭐⭐ 工具限制 heredoc + SSH 多层引号
3 SSH SIGHUP 杀进程 ⭐⭐⭐⭐⭐ 操作系统 PTY + 信号机制
4 accessToken 不匹配 ⭐⭐⭐⭐ 配置一致性 认证令牌
5 启动时序 ⭐⭐⭐ 部署流程 Admin 先于 Executor
6 Maven 日志缓冲 ⭐⭐ 工具行为 spring-boot:run 模式

七、学习资源推荐

资源 地址 说明
官方文档 https://www.xuxueli.com/xxl-job/ 最权威的参考文档
GitHub 仓库 https://github.com/xuxueli/xxl-job 源码 + Issues
Gitee 仓库 https://gitee.com/xuxueli0323/xxl-job 国内镜像,访问更快
在线 Demo https://www.xuxueli.com/xxl-job/#/quickstart 无需部署即可体验
源码结构分析 xxl-job/doc/ 目录 源码自带文档

学习路线建议

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    推荐学习路线                                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Week 1  基础入门                                                │
│  ├─ [Day 1-2] 阅读官方文档,搭建环境                               │
│  ├─ [Day 3] 完成实操5: 启动调度中心                               │
│  ├─ [Day 4] 完成实操9: 简单任务开发                               │
│  └─ [Day 5] 完成实操12-13: Web界面操作                            │
│                                                                 │
│  Week 2  进阶提升                                                │
│  ├─ [Day 1] Cron 表达式精通                                      │
│  ├─ [Day 2] 路由策略对比测试                                      │
│  ├─ [Day 3] 分片广播任务实战                                      │
│  ├─ [Day 4] GLUE 模式探索                                       │
│  └─ [Day 5] 失败重试与告警配置                                    │
│                                                                 │
│  Week 3  实战应用                                                │
│  ├─ [Day 1-2] 数据同步任务开发                                    │
│  ├─ [Day 3-4] 集群部署实战                                       │
│  └─ [Day 5] 性能优化实践                                         │
│                                                                 │
│  Week 4  生产落地                                                │
│  ├─ [Day 1-2] 接入实际业务项目                                    │
│  ├─ [Day 3] 监控告警完善                                          │
│  ├─ [Day 4] 安全加固                                             │
│  └─ [Day 5] 文档沉淀 + 团队分享                                   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

附录A:完整配置参考

xxl-job-admin application.properties(完整版)

properties 复制代码
# ============================================
# 服务端口
# ============================================
server.port=8080
server.servlet.context-path=/xxl-job-admin

# ============================================
# 数据库配置
# ============================================
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# HikariCP
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.maximum-pool-size=30
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=HikariCP-XXL-JOB
spring.datasource.hikari.max-lifetime=900000
spring.datasource.hikari.connection-timeout=10000
spring.datasource.hikari.connection-test-query=SELECT 1

# ============================================
# 邮件配置
# ============================================
spring.mail.host=smtp.qq.com
spring.mail.port=25
spring.mail.username=your_email@qq.com
spring.mail.password=your_auth_code
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
spring.mail.from=your_email@qq.com

# ============================================
# XXL-JOB 核心配置
# ============================================
xxl.job.accessToken=
xxl.job.i18n=zh_CN
xxl.job.triggerpool.fast.max=200
xxl.job.triggerpool.slow.max=100
xxl.job.logretentiondays=30

# ============================================
# MyBatis 配置
# ============================================
mybatis.mapper-locations=classpath:/mybatis-mapper/*Mapper.xml
mybatis.type-aliases-package=com.xxl.job.admin.core.model

# ============================================
# 日志配置
# ============================================
logging.config=classpath:logback.xml

执行器 application.yml(完整版)

yaml 复制代码
server:
  port: 8081

spring:
  application:
    name: xxl-job-executor-demo

# ============================================
# XXL-JOB 执行器配置
# ============================================
xxl:
  job:
    admin:
      addresses: http://127.0.0.1:8080/xxl-job-admin
    executor:
      appname: xxl-job-executor-demo
      address:
      ip:
      port: 9999
      logpath: /data/applogs/xxl-job/jobhandler
      logretentiondays: 30
    accessToken: default_token

附录B:Cron 表达式速查表

复制代码
┌─────────────────────────────────────────────────────────────────┐
│  Cron 表达式: 秒 分 时 日 月 周                                    │
│  范围:        0-59 0-59 0-23 1-31 1-12 1-7(1=SUN)               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ⏱ 每秒    →  * * * * * ?                                       │
│  ⏱ 每5秒   →  0/5 * * * * ?                                     │
│  ⏱ 每30秒  →  0/30 * * * * ?                                    │
│                                                                 │
│  🕐 每分钟   →  0 * * * * ?                                      │
│  🕐 每5分钟  →  0 0/5 * * * ?                                    │
│  🕐 每30分钟 →  0 0/30 * * * ?                                   │
│                                                                 │
│  🕑 每小时   →  0 0 * * * ?                                      │
│  🕑 每2小时  →  0 0 0/2 * * ?                                    │
│                                                                 │
│  🌅 每天0点   →  0 0 0 * * ?                                     │
│  🌅 每天2点   →  0 0 2 * * ?                                     │
│  🌅 每天8:30  →  0 30 8 * * ?                                    │
│                                                                 │
│  📅 每周一9点  →  0 0 9 ? * 2                                    │
│  📅 工作日9点  →  0 0 9 ? * 2-6                                  │
│  📅 每月1号   →  0 0 0 1 * ?                                     │
│  📅 每月最后一天 → 0 0 0 L * ?                                   │
│                                                                 │
│  🕘 9-18点每小时 → 0 0 9-18 * * ?                                │
│  🕘 工作日9-18点 → 0 0 9-18 ? * 2-6                              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

附录C:版本差异对照表

特性 2.3.x 2.4.x 2.5.0
JDK 要求 JDK 8 JDK 8 JDK 17
Spring Boot 2.x 2.x 3.x
调度中心 UI Layui Layui Layui (升级)
GLUE 模式 Java/Shell/Python/PHP/NodeJS/PS 同 2.3 同 2.3
路由策略 9种 10种 10种
分片广播
AccessToken
国际化
任务超时控制
失败重试

版本选择建议:

  • 新项目 → 直接使用 2.5.0+ (JDK 17)
  • 老项目 JDK 8 → 使用 2.4.2 (最后一个 JDK 8 版本)
  • 升级需注意 Spring Boot 3.x 的 javax.*jakarta.* 包名变更

文档版本 : v1.0 | 创建日期 : 2026-06-03

适用场景 : XXL-JOB 从零到生产级部署的完整学习路径

作者签名: 实战系列,基于官方文档和实践经验

相关推荐
m0_736034854 小时前
ceph分布式存储
分布式·ceph
冷色调的咖啡师4 小时前
1.大数据架构技术 上——搭建分布式Hadoop集群
大数据·linux·hadoop·分布式·hdfs·架构·yarn
坤昱21 小时前
cfs调度类深入解刨——最新内核细节分析5
linux·分布式·cfs调度·eevdf调度·linux调度·linux技术·kernel最新版本内容
AI人工智能+电脑小能手21 小时前
【大白话说Java面试题 第91题】【Mysql篇】第21题:分布式锁的使用场景和原理?
java·数据库·分布式·mysql·面试
JAVA社区21 小时前
Java高级全套教程(十三)—— 分布式锁超详细实战详解(原理+三种方案企业级落地)
java·开发语言·分布式·spring cloud·面试·java-zookeeper
Leo18721 小时前
分布式事务
java·分布式·分布式事务
潮起鲸落入海1 天前
ceph分布式存储认证和授权,块存储管理
分布式·ceph
ZPC82101 天前
前馈补偿原理 + 分类 + 公式 + 工程实现(配合 PID 使用,从根源减轻闭环收敛压力)
人工智能·分布式·机器人
闪电悠米1 天前
黑马点评-分布式锁-02_simple_redis_lock_setnx
java·数据库·spring boot·redis·分布式·缓存·wpf