【Spring Boot实战系列】告别@Scheduled!XXL-JOB分布式定时任务实战
开源仓库:youlai-boot
前言
本系列文章基于 youlai-boot 开源项目实践,一个开箱即用的 Spring Boot 后台管理系统。
定时任务这东西,小项目用 Spring 的 @Scheduled 注解确实够用。但项目一上规模,问题就来了:
- 多实例重复执行:部署了3个实例,任务跑了3遍
- 无法动态调整:改个 Cron 表达式要重新发版
- 失败无重试:任务执行失败只能手动触发
- 缺乏监控:任务执行情况两眼一抹黑
XXL-JOB 这些问题都解决了,而且学习成本很低。youlai-boot 直接集成好了,开箱即用。
XXL-JOB 是什么
轻量级分布式任务调度平台,核心特性:
| 特性 | 说明 |
|---|---|
| 简单 | 一个 @XxlJob 注解定义任务 |
| 动态管理 | Web 控制台管理任务,无需重启 |
| 分布式 | 多执行器集群,自动注册发现 |
| 故障转移 | 执行器挂了自动切换其他节点 |
| 分片广播 | 大数据量分片并行处理 |
| 失败重试 | 配置重试次数,自动重试 |
架构上分两部分:调度中心(xxl-job-admin)负责管理和触发,执行器(你的应用)负责实际执行。
环境搭建
数据库初始化
XXL-JOB 需要几张表存任务配置和日志,官方脚本:
https://gitee.com/xuxueli0323/xxl-job/blob/2.4.0/doc/db/tables_xxl_job.sql
核心表:
xxl_job_info:任务配置xxl_job_log:执行日志xxl_job_registry:执行器注册
Docker 部署调度中心
youlai-boot 的 docker-compose 已经配好:
cd youlai-boot/docker
docker-compose -p youlai-boot up -d
或者单独部署:
docker run -d --name xxl-job-admin \
-e PARAMS="--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?..." \
-p 8080:8080 \
xuxueli/xxl-job-admin:2.4.0
访问 http://localhost:8080/xxl-job-admin,默认 admin/123456。
项目集成
添加依赖
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>3.2.0</version>
</dependency>
配置文件
xxl:
job:
enabled: false # 开发环境关闭,生产开启
admin:
addresses: http://127.0.0.1:8080/xxl-job-admin
accessToken: default_token
executor:
appname: xxl-job-executor-youlai-boot
port: 9999
logpath: /data/applogs/xxl-job
logretentiondays: 30
enabled 配置很实用,开发环境不用启动 XXL-JOB,避免依赖问题。
youlai-boot 已经封装好 XxlJobConfig,用 @ConditionalOnProperty 条件装配,只有配置了 enabled=true 才初始化执行器。
任务开发:一个注解的事
最简示例
@Component
public class SampleJob {
@XxlJob("demoJobHandler")
public void demoJobHandler() {
log.info("任务执行了");
}
}
然后在调度中心添加任务,JobHandler 填 demoJobHandler,配置 Cron 表达式,启动。
任务参数
@XxlJob("paramJob")
public void paramJob() {
String param = XxlJobHelper.getJobParam();
log.info("任务参数: {}", param);
}
参数在调度中心配置,支持 JSON 格式。
分片任务:大数据量并行处理
@XxlJob("shardingJob")
public void shardingJob() {
int index = XxlJobHelper.getShardIndex(); // 当前分片序号
int total = XxlJobHelper.getShardTotal(); // 总分片数
// 每个执行器处理 1/total 的数据
List<Order> orders = orderMapper.selectList(
new LambdaQueryWrapper<Order>()
.apply("MOD(id, {0}) = {1}", total, index)
);
// 处理订单...
}
调度中心配置路由策略为「分片广播」,一次调度会触发所有执行器,每个拿到不同的分片参数。
调度中心配置要点
添加执行器
执行器管理 → 新增
AppName 要和配置文件的 xxl.job.executor.appname 一致,注册方式选「自动注册」。
添加任务
任务管理 → 新增
关键配置:
| 配置项 | 说明 |
|---|---|
| JobHandler | 代码里 @XxlJob 的值 |
| Cron | 调度表达式 |
| 路由策略 | 单机选 FIRST,分片选 SHARDING_BROADCAST |
| 阻塞策略 | 单机串行/并行 |
| 失败重试 | 重试次数 |
路由策略说明
| 策略 | 适用场景 |
|---|---|
| FIRST | 单机任务 |
| ROUND | 负载均衡 |
| FAILOVER | 高可用,故障自动切换 |
| SHARDING_BROADCAST | 分片并行处理 |
常见问题排查
执行器未注册
检查清单:
xxl.job.enabled是否为 true- 执行器能否访问调度中心(网络问题)
- 端口 9999 是否被占用
- AppName 是否与调度中心配置一致
任务不执行
检查:
- Cron 表达式是否正确
- 任务是否启动
- 执行器是否有在线实例
- 查看调度日志
执行失败
查看任务执行日志,通常是业务代码抛异常。JobHandler 名称要和代码一致。
时区问题
设置 JVM 时区:
spring:
jackson:
time-zone: Asia/Shanghai
从 @Scheduled 迁移的步骤
- 梳理现有任务:列出所有 @Scheduled 方法
- 添加依赖和配置:引入 xxl-job-core
- 改造任务方法:把 @Scheduled 改成 @XxlJob
- 调度中心配置:添加执行器和任务
- 测试验证:先在测试环境跑通
- 灰度上线:先上一台实例观察
迁移工作量不大,主要是配置和测试。
最佳实践
幂等性设计
任务可能重复执行(调度中心重启等),业务逻辑要保证幂等。用乐观锁或状态判断。
@XxlJob("timeoutJob")
public void timeoutJob() {
// 只查待处理状态的数据,已处理的不会重复
List<Order> orders = orderMapper.selectList(
new LambdaQueryWrapper<Order>()
.eq(Order::getStatus, "PENDING")
.lt(Order::getExpireTime, new Date())
);
}
异常处理
@XxlJob("syncJob")
public void syncJob() {
try {
doSync();
XxlJobHelper.handleSuccess("同步成功");
} catch (Exception e) {
log.error("同步失败", e);
XxlJobHelper.handleFail("同步失败: " + e.getMessage());
}
}
性能优化
- 大数据量用分片
- 避免循环单条处理数据库
- 耗时操作异步化
结语
从 @Scheduled 迁移到 XXL-JOB,前期投入一点时间,后期维护轻松很多。任务管理可视化、失败重试、分片执行、故障转移,这些能力生产环境真的需要。
youlai-boot 已经集成好了,配置一下就能用。如果你的项目还在用 @Scheduled,建议早点换。
更多推荐




所有评论(0)