本文适合刚接触分布式任务调度的开发者,以及准备面试的同学。从核心概念到实战部署,代码简洁易懂,快速上手企业级调度中心。
一、什么是调度中心?
一句话: 调度中心就是"定时任务管理平台",统一管理和监控所有定时任务。
传统方式: 调度中心方式:
┌─────────┐ ┌─────────────────────┐
│ 服务A │ @Scheduled │ 调度中心 │
│ 服务B │ cron表达式 │ (XXL-JOB) │
│ 服务C │ 分散在各服务中 │ ↓ │
└─────────┘ │统一管理、监控、报警 │
└─────────────────────┘
↓
┌─────────────────────┐
│服务A 服务B 服务C │
└─────────────────────┘
常见调度中心对比:
| Spring Task | 简单、单机 | 简单定时任务 |
| Quartz | 功能强大、复杂 | 单体应用 |
| XXL-JOB | 可视化、分布式、易用 | 企业级首选(推荐) |
| Elastic-Job | 分片、弹性 | 大数据量任务 |
二、XXL-JOB核心架构
┌─────────────────────────────────────────────────────────────┐
│ 调度中心(Admin) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │任务管理 │ │调度日志 │ 执行器管理│ │用户管理 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────┘
↓ 调度触发
┌─────────────────────────────────────────────────────────────┐
│ 执行器(Executor) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │服务A执行器 │ │服务B执行器 │ │服务C执行器 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
核心概念:
-
调度中心:任务管理后台,负责触发任务
-
执行器:实际执行任务的服务,注册到调度中心
-
任务:具体的业务逻辑(一个Java方法)
三、快速部署(10分钟搞定)
3.1 下载源码
# GitHub地址
git clone https://github.com/xuxueli/xxl-job.git
# 目录结构
xxl-job/
├── doc/ # 文档和SQL脚本
├── xxl-job-admin/ # 调度中心(后台管理)
└── xxl-job-executor-samples/ # 执行器示例
3.2 初始化数据库
— 执行 doc/db/tables_xxl_job.sql
— 创建8张表:
— 1. xxl_job_info:任务信息
— 2. xxl_job_log:调度日志
— 3. xxl_job_log_report:报表
— 4. xxl_job_logglue:GLUE日志
— 5. xxl_job_group:执行器分组
— 6. xxl_job_user:用户
— 7. xxl_job_lock:分布式锁
3.3 启动调度中心
# xxl-job-admin/src/main/resources/application.properties
server.port=8080
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456
# 调度中心通讯TOKEN(执行器需要配置相同TOKEN)
xxl.job.accessToken=xxl-job-token
启动 XxlJobAdminApplication,访问 http://localhost:8080
-
账号:admin
-
密码:123456
四、创建执行器(Spring Boot项目)
4.1 引入依赖
<!– pom.xml –>
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.4.0</version>
</dependency>
4.2 配置文件
# application.yml
server:
port: 8081
xxl:
job:
admin:
addresses: http://127.0.0.1:8080/xxl-job-admin # 调度中心地址
executor:
appname: my-executor # 执行器名称
port: 9999 # 执行器端口(0则自动)
logpath: /data/applogs/xxl-job/jobhandler # 日志路径
logretentiondays: 30 # 日志保留天数
accessToken: xxl-job-token # 与调度中心一致
4.3 执行器配置类
/**
* XXL-JOB执行器配置
*/
@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.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 executor = new XxlJobSpringExecutor();
executor.setAdminAddresses(adminAddresses);
executor.setAppname(appname);
executor.setPort(port);
executor.setLogPath(logPath);
executor.setLogRetentionDays(logRetentionDays);
executor.setAccessToken(accessToken);
return executor;
}
}
五、任务开发实战
5.1 方式一:Bean模式(推荐)
/**
* 任务Handler:Bean模式
* 在调度中心配置JobHandler名称即可
*/
@Component
public class SampleJobHandler {
private static final Logger log = LoggerFactory.getLogger(SampleJobHandler.class);
/**
* 简单任务示例
* 调度中心JobHandler填写:sampleJobHandler
*/
@XxlJob("sampleJobHandler")
public void sampleJobHandler() throws Exception {
log.info("XXL-JOB任务执行:sampleJobHandler");
// 业务逻辑
for (int i = 0; i < 5; i++) {
log.info("执行进度:{}/5", i + 1);
Thread.sleep(1000);
}
log.info("任务执行完成");
}
/**
* 带参数的任务
* 调度中心运行参数填写:hello world
*/
@XxlJob("paramJobHandler")
public void paramJobHandler() throws Exception {
// 获取调度中心传入的参数
String param = XxlJobHelper.getJobParam();
log.info("接收到参数:{}", param);
// 业务处理
XxlJobHelper.log("参数处理完成:" + param);
}
/**
* 分片任务(大数据量并行处理)
* 路由策略使用:分片广播
*/
@XxlJob("shardingJobHandler")
public void shardingJobHandler() throws Exception {
// 获取分片参数
int shardIndex = XxlJobHelper.getShardIndex(); // 当前分片序号
int shardTotal = XxlJobHelper.getShardTotal(); // 分片总数
log.info("分片参数:当前第{}片,共{}片", shardIndex, shardTotal);
// 业务逻辑:按分片处理数据
// 例如:shardTotal=3,则每个执行器处理1/3的数据
List<Integer> dataList = getAllData();
for (int i = 0; i < dataList.size(); i++) {
if (i % shardTotal == shardIndex) {
// 当前执行器负责的数据
processData(dataList.get(i));
}
}
}
/**
* 返回执行结果(成功/失败)
*/
@XxlJob("resultJobHandler")
public void resultJobHandler() throws Exception {
try {
// 业务逻辑
boolean success = doBusiness();
if (success) {
XxlJobHelper.handleSuccess("执行成功");
} else {
XxlJobHelper.handleFail("执行失败:业务处理异常");
}
} catch (Exception e) {
XxlJobHelper.handleFail("执行异常:" + e.getMessage());
}
}
}
5.2 方式二:GLUE模式(Java代码在线编辑)
适用场景:任务逻辑需要频繁修改,不想重新发布服务
操作步骤:
1. 调度中心创建任务,路由策略选择"第一个"或"轮询"
2. 运行模式选择"GLUE(Java)"
3. 点击"GLUE IDE"在线编写代码
4. 保存后立即生效,无需重启
// GLUE模式代码示例(在线编辑)
package com.xxl.job.service.handler;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DemoGlueJobHandler {
private static final Logger log = LoggerFactory.getLogger(DemoGlueJobHandler.class);
@XxlJob("demoGlueJobHandler")
public void execute() throws Exception {
log.info("GLUE模式任务执行,代码可在线修改");
// 业务逻辑…
}
}
六、调度中心配置详解
6.1 创建任务
1. 打开调度中心:http://localhost:8080
2. 任务管理 → 新增
必填配置:
├── 任务描述:用户数据同步任务
├── 执行器:my-executor(选择刚注册的执行器)
├── JobHandler:sampleJobHandler(与@XxlJob注解值一致)
├── Cron:0 0 2 * * ? (每天凌晨2点执行)
└── 路由策略:轮询
6.2 路由策略说明
| 第一个 | 固定选择第一个执行器 | 单机执行 |
| 轮询 | 依次轮流执行 | 负载均衡 |
| 随机 | 随机选择执行器 | 负载均衡 |
| 一致性HASH | 相同任务始终到同一执行器 | 有状态任务 |
| 最不经常使用 | 选择使用最少的执行器 | 动态负载 |
| 最近最久未使用 | 选择最久未使用的执行器 | 动态负载 |
| 故障转移 | 忽略超时的,选择下一个 | 高可用 |
| 忙碌转移 | 忽略执行中的,选择下一个 | 高可用 |
| 分片广播 | 广播到所有执行器,分片执行 | 大数据量 |
6.3 Cron表达式速查
格式:秒 分 时 日 月 周
常用示例:
┌──────────────────────────────────────────────────────────┐
│ 0 0/5 * * * ? 每5分钟执行 │
│ 0 0 2 * * ? 每天凌晨2点 │
│ 0 0 1 * * 1 每周一凌晨1点 │
│ 0 0 0 1 * ? 每月1号零点 │
│ 0 0 0 * * ? 每天零点 │
│ 0 30 9 * * ? 每天9:30 │
│ 0 0 9-18 * * ? 每天9-18点整点 │
│ 0 0/30 9-18 * * ? 每天9-18点,每30分钟 │
└──────────────────────────────────────────────────────────┘
七、实战场景示例
7.1 场景一:数据同步任务
/**
* 每天凌晨同步订单数据
*/
@Component
public class OrderSyncHandler {
@Autowired
private OrderService orderService;
@XxlJob("orderSyncJob")
public void orderSyncJob() throws Exception {
// 获取参数:同步天数
String param = XxlJobHelper.getJobParam();
int days = param != null ? Integer.parseInt(param) : 1;
// 计算日期范围
LocalDate endDate = LocalDate.now();
LocalDate startDate = endDate.minusDays(days);
XxlJobHelper.log("开始同步订单:{} 到 {}", startDate, endDate);
// 分页查询并同步
int page = 1;
int pageSize = 1000;
int total = 0;
while (true) {
List<Order> orders = orderService.queryByPage(startDate, endDate, page, pageSize);
if (orders.isEmpty()) break;
// 批量处理
orderService.batchSync(orders);
total += orders.size();
XxlJobHelper.log("已同步:{}条", total);
page++;
}
XxlJobHelper.handleSuccess("同步完成,共" + total + "条");
}
}
7.2 场景二:超时订单处理
/**
* 每5分钟检查超时未支付订单
*/
@Component
public class TimeoutOrderHandler {
@Autowired
private OrderService orderService;
@XxlJob("timeoutOrderJob")
public void timeoutOrderJob() throws Exception {
// 查询30分钟内未支付的订单
LocalDateTime timeout = LocalDateTime.now().minusMinutes(30);
List<Order> timeoutOrders = orderService.queryUnpaidBefore(timeout);
XxlJobHelper.log("发现超时订单:{}笔", timeoutOrders.size());
int successCount = 0;
for (Order order : timeoutOrders) {
try {
orderService.cancelOrder(order.getId());
successCount++;
} catch (Exception e) {
XxlJobHelper.log("取消订单失败:{},原因:{}", order.getId(), e.getMessage());
}
}
XxlJobHelper.handleSuccess("处理完成:成功" + successCount + "笔");
}
}
7.3 场景三:报表生成
/**
* 每天生成日报表(分片广播模式)
*/
@Component
public class ReportHandler {
@XxlJob("dailyReportJob")
public void dailyReportJob() throws Exception {
int shardIndex = XxlJobHelper.getShardIndex();
int shardTotal = XxlJobHelper.getShardTotal();
XxlJobHelper.log("分片 {}/{} 开始生成报表", shardIndex, shardTotal);
// 按分片处理不同业务线
List<String> businessLines = Arrays.asList("order", "user", "product", "finance");
for (int i = 0; i < businessLines.size(); i++) {
if (i % shardTotal == shardIndex) {
String line = businessLines.get(i);
XxlJobHelper.log("处理业务线:{}", line);
generateReport(line);
}
}
XxlJobHelper.handleSuccess("报表生成完成");
}
private void generateReport(String businessLine) {
// 生成报表逻辑…
}
}
7.4 场景四:邮件/短信提醒
/**
* 工作日早上9点发送提醒
*/
@Component
public class ReminderHandler {
@Autowired
private EmailService emailService;
@XxlJob("morningReminderJob")
public void morningReminderJob() throws Exception {
// 判断是否工作日
if (!isWorkDay(LocalDate.now())) {
XxlJobHelper.log("今天不是工作日,跳过");
return;
}
// 获取需要提醒的用户
List<User> users = getReminderUsers();
for (User user : users) {
try {
emailService.sendReminder(user.getEmail(), "早安提醒");
XxlJobHelper.log("发送成功:{}", user.getEmail());
} catch (Exception e) {
XxlJobHelper.log("发送失败:{},原因:{}", user.getEmail(), e.getMessage());
}
}
}
private boolean isWorkDay(LocalDate date) {
DayOfWeek day = date.getDayOfWeek();
return day != DayOfWeek.SATURDAY && day != DayOfWeek.SUNDAY;
}
}
八、高级功能
8.1 任务依赖(DAG)
场景:任务A完成后,触发任务B和C,B和C都完成后触发D
配置方式:
1. 任务B、C的"父任务ID"填写A的ID
2. 任务D的"父任务ID"填写B和C的ID
效果:
A
/ \\
B C
\\ /
D
8.2 邮件报警配置
# 调度中心 application.properties
spring.mail.host=smtp.qq.com
spring.mail.port=465
spring.mail.username=your-email@qq.com
spring.mail.password=your-password
spring.mail.properties.mail.smtp.ssl.enable=true
# 任务失败时发送邮件到:
xxl.job.alarm.email=admin@example.com
8.3 运行报表
调度中心 → 运行报表
可查看:
├── 调度次数趋势
├── 执行结果分布(成功/失败/失败重启)
├── 执行器在线状态
└── 任务执行耗时统计
九、常见问题排查
问题1:执行器注册失败
# 检查项:
1. 调度中心地址是否正确
2. TOKEN是否与调度中心一致
3. 执行器名称(appname)是否在调度中心创建
4. 网络是否通畅
# 日志查看:
tail -f /data/applogs/xxl-job/jobhandler/xxl-job-executor.log
问题2:任务一直不执行
# 检查项:
1. 任务状态是否为"运行中"
2. Cron表达式是否正确(在线工具:cron.qqe2.com)
3. 执行器是否在线
4. 路由策略是否匹配
问题3:任务执行超时
# 解决方案:
1. 增大任务超时时间(默认30分钟)
2. 任务内记录日志:XxlJobHelper.log()
3. 大任务改为分片广播模式
十、面试高频问题
Q1:XXL-JOB的架构是怎样的?
答: 采用中心化架构,由调度中心和执行器两部分组成:
-
调度中心:负责任务管理、触发调度、日志管理
-
执行器:注册到调度中心,接收调度请求执行任务
调度中心通过HTTP调用执行器,执行器回调执行结果。
Q2:XXL-JOB如何保证任务不重复执行?
答: 三种机制
路由策略:分片广播模式下,每个执行器只处理自己的分片
分布式锁:调度中心使用数据库锁保证同一任务同一时刻只触发一次
阻塞策略:默认"丢弃后续调度",避免任务堆积
Q3:XXL-JOB和Quartz的区别?
答:
| 架构 | 嵌入式 | 中心化 |
| 可视化 | 无 | 有 |
| 分布式 | 需自己实现 | 原生支持 |
| 路由策略 | 无 | 9种策略 |
| 日志管理 | 无 | 完整日志 |
| 报警 | 无 | 邮件报警 |
| 学习成本 | 中等 | 低 |
Q4:如何实现任务失败重试?
答:
调度中心 → 任务配置 → 失败重试次数:3
任务代码中抛出异常即为失败:
if (执行失败) {
throw new RuntimeException("任务失败");
}
调度中心会自动重试指定次数
Q5:分片广播的使用场景?
答: 适用于大数据量并行处理
场景:同步100万条数据
单机:1个执行器处理100万条,耗时长
分片:3个执行器各处理33万条,并行执行
代码获取分片参数:
int shardIndex = XxlJobHelper.getShardIndex(); // 0,1,2
int shardTotal = XxlJobHelper.getShardTotal(); // 3
十一、最佳实践
任务幂等:任务可重复执行,结果一致
日志记录:关键步骤使用XxlJobHelper.log()
超时设置:根据任务耗时合理设置
报警配置:生产环境必须配置邮件报警
分片处理:大数据量任务使用分片广播
参数传递:通过JobParam传递配置,避免硬编码
十二、推荐阅读
-
XXL-JOB官方文档
-
XXL-JOB GitHub
-
Cron表达式在线生成器




