@Scheduled 在单机环境下确实够用,可一旦上了多实例部署,问题就来了——同一个任务每个机器都会执行一遍,这显然不是我们想要的。XXL-JOB 正是为解决分布式任务调度场景而诞生的轻量级分布式任务调度平台。它提供了可视化的任务管理、执行日志、失败告警等能力,如今在生产环境下,它已经是很多团队实现分布式定时任务的主流选择。

一、XXL-JOB 分布式调度架构解析
调度中心(xxl-job-admin)
│ 调度任务 ↓
├── 执行器 A(任务1、任务2)
├── 执行器 B(任务1、任务3)
└── 执行器 C(任务2、任务4)
核心概念说明:
- 调度中心:负责管理任务配置、触发调度、查看日志,是整个分布式任务调度系统的“大脑”。
- 执行器:实际执行业务逻辑的应用程序,可以理解成“干活的工人”,每个执行器可注册多个任务。
- 任务:具体要执行的业务代码,比如超时订单处理、每日数据统计等定时任务。
二、部署调度中心(xxl-job-admin)
1. 下载官方源码
git clone https://github.com/xuxueli/xxl-job.git
2. 初始化调度数据库
-- 执行 xxl-job/doc/db/tables_xxl_job.sql -- 会创建 xxl_job 数据库及相关表
3. 修改调度中心配置
# xxl-job-admin/src/main/resources/application.properties server.port=8080 spring.datasource.url=jdbc:mysql://localhost:3306/xxl_job?useUnicode=true&characterEncoding=utf-8 spring.datasource.username=root spring.datasource.password=123456 # 登录账号 xxl.job.login.username=admin xxl.job.login.password=123456
4. 启动调度服务
mvn clean package -DskipTests ja va -jar xxl-job-admin/target/xxl-job-admin-2.4.0.jar
访问 https://localhost:8080/xxl-job-admin,登录后就能看到分布式任务调度管理界面了,一切就绪。
三、SpringBoot 集成 XXL-JOB 执行器
1. 引入 XXL-JOB 核心依赖
com.xuxueli xxl-job-core 2.4.0
2. 配置执行器参数
xxl:
job:
admin:
# 调度中心地址
addresses: https://localhost:8080/xxl-job-admin
executor:
# 执行器名称
appname: seckill-executor
# 执行器端口
port: 9999
# 执行器注册地址
address: ""
ip: ""
logpath: /data/xxl-job/logs
logretentiondays: 30
accessToken: # 调度中心通信 Token
3. 编写执行器配置类
@Configuration
public class XxlJobConfig {
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appname;
@Value("${xxl.job.executor.port}")
private int port;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
XxlJobSpringExecutor executor = new XxlJobSpringExecutor();
executor.setAdminAddresses(adminAddresses);
executor.setAppname(appname);
executor.setPort(port);
executor.setLogPath("/data/xxl-job/logs");
executor.setLogRetentionDays(30);
return executor;
}
}
四、编写分布式任务
1. Bean 模式(推荐方式)
这是最常用的开发方式,直接在方法上标注 @XxlJob 注解,框架会自动发现并注册到调度中心。看看这个超时未支付订单处理的实战例子:
@Component
public class SeckillTasks {
private static final Logger log = LoggerFactory.getLogger(SeckillTasks.class);
/**
* 超时未支付订单处理
*/
@XxlJob("timeoutOrderHandler")
public ReturnT timeoutOrderHandler(String param) {
log.info("开始处理超时未支付订单...");
// 查询超时订单
List timeoutOrders = orderService.list(
new LambdaQueryWrapper()
.eq(Order::getStatus, 0)
.lt(Order::getCreateTime, LocalDateTime.now().minusMinutes(10))
);
for (Order order : timeoutOrders) {
// 取消订单
order.setStatus(2);
orderService.updateById(order);
// 归还库存
productService.restoreStock(order.getProductId(), order.getQuantity());
}
log.info("处理完成,共取消 {} 笔订单", timeoutOrders.size());
return ReturnT.SUCCESS;
}
/**
* 每日数据统计
*/
@XxlJob("dailyReport")
public ReturnT dailyReport(String param) {
log.info("生成日报表...");
// 报表生成逻辑
return ReturnT.SUCCESS;
}
}
2. 分片广播模式(多机器协作处理)
当数据量很大时,单机处理太慢,这时候就轮到分片广播上场了。它会自动把数据切分给多个执行器并行处理,大幅提升分布式任务的处理效率:
@XxlJob("shardingJob")
public ReturnT shardingJob(String param) {
// 获取当前执行器的分片信息
ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
int index = shardingVO.getIndex(); // 当前执行器编号(从0开始)
int total = shardingVO.getTotal(); // 执行器总数
// 查询所有需要处理的数据
List userIds = getAllUserIds();
// 只处理分配给自己的那部分
for (int i = index; i < userIds.size(); i += total) {
processUser(userIds.get(i));
}
log.info("分片 {}/{} 处理完成,处理 {} 条", index + 1, total,
userIds.size() / total);
return ReturnT.SUCCESS;
}
五、在管理台配置调度任务
登录调度中心后,先添加执行器:
执行器管理 → 新增执行器 AppName: seckill-executor 名称: 秒杀系统执行器 注册方式: 自动注册
然后再添加定时任务:
任务管理 → 新增任务 执行器: seckill-executor 任务描述: 超时订单处理 调度类型: cron Cron: 0 */1 * * * ? ← 每分钟执行一次 运行模式: Bean JobHandler: timeoutOrderHandler
六、任务参数动态传递
有时候任务需要动态参数,比如指定统计日期或数量限制。XXL-JOB 支持在调度中心配置时传递参数,任务代码里直接接收即可,让分布式任务调度更加灵活:
@XxlJob("paramJob")
public ReturnT paramJob(String param) {
// param 是在调度中心配置任务时填写的参数
log.info("接收参数: {}", param);
if (StringUtils.isNotBlank(param)) {
JSONObject json = JSONObject.parseObject(param);
String date = json.getString("date");
int limit = json.getInteger("limit");
// 使用参数执行任务
}
return ReturnT.SUCCESS;
}
对应的调度中心任务配置里填入 JSON 格式参数:
{"date": "2026-07-03", "limit": 1000}
七、任务执行日志查看
调试或者排查问题时,日志是救命稻草。XXL-JOB 提供了专用的日志 API,可以在调度中心实时查看每台机器上的执行日志,极大方便了分布式任务调度的运维排查:
@XxlJob("logJob")
public ReturnT logJob(String param) {
XxlJobLogger.log("任务开始执行...");
try {
// 业务逻辑
XxlJobLogger.log("处理第 1 批数据,共 100 条");
Thread.sleep(1000);
XxlJobLogger.log("处理第 2 批数据,共 100 条");
Thread.sleep(1000);
XxlJobLogger.log("任务执行完成");
return ReturnT.SUCCESS;
} catch (Exception e) {
XxlJobLogger.log("任务执行失败: {}", e.getMessage());
return ReturnT.FAIL;
}
}
八、@Scheduled vs Quartz vs XXL-JOB 对比
很多朋友会纠结该选哪个,这里直接甩一张对比表,优缺点一目了然,帮你快速选型:
| 对比维度 | @Scheduled | Quartz | XXL-JOB |
|---|---|---|---|
| 部署方式 | 内嵌在应用中 | 内嵌在应用中 | 调度中心 + 执行器分离 |
| 可视化界面 | ❌ | ❌ | ✅ 管理界面 |
| 分布式支持 | ❌ 每个实例都执行 | ❌ 需自己实现 | ✅ 天然支持 |
| 失败告警 | ❌ | ❌ | ✅ 邮件告警 |
| 动态修改 | ❌ 改代码重启 | ❌ | ✅ 在线修改 |
| 适用场景 | 简单单机任务 | 复杂调度逻辑 | 生产环境分布式 |
九、秒杀系统集成建议与最佳实践
最后聊点实战中的经验。秒杀系统对分布式任务调度的要求其实很明确——定时清理过期活动、处理超时订单、生成报表、数据归档。下面是我在实际项目中常用的调度策略:
每天零点 → 清理过期活动数据 → 日清理任务 每分钟 → 检查超时未支付订单 → 超时订单处理 每月1号 → 生成月度报表 → 月报任务 大促后 → 数据归档 → 归档任务(分片广播)
建议:项目初期如果只跑单机,用 @Scheduled 快速实现完全没问题,等系统上线稳定、需要多实例部署时,再平滑迁移到 XXL-JOB 分布式任务调度平台也不迟。毕竟工具服务于业务,不要为了用而用,选择最适合当前阶段的分布式定时任务方案即可。
