欢迎光临
我们一直在努力

XXLJob分布式定时任务实战指南

XXL-Job 分布式定时任务实战指南

定时任务从单机的 @Scheduled 到分布式的集群执行,中间全是坑:重复执行、漏执行、动态调度、任务日志。XXL-Job 是国产分布式任务调度平台的事实标准,也是 CSDN Java 板块最热门的话题。本文从定时任务的痛点讲起,覆盖 XXL-Job 架构、部署、任务类型(Bean/GLUE)、路由策略、分片广播、调度与生命周期、实战案例、常见坑,全程带可运行代码。


一、定时任务的痛点

1.1 单机定时任务的局限

// 单机:Spring 的 @Scheduled
@Scheduled(cron = "0 0 2 * * ?")
public void dailyReport() {
// 每天凌晨 2 点跑报表
}

问题:

1. 多实例部署 → 每个实例都执行 → 任务重复跑!
2. 没有监控 → 任务失败/漏执行不知道
3. 没有管理界面 → 想手动触发/暂停做不到
4. 没有日志 → 排查靠猜
5. 不能动态调整 → 改 cron 要改代码重新部署

1.2 分布式任务调度要解决什么

能力说明
集群执行不重复 一台执行,其他不执行(或分片)
动态调度 控制台改 cron,不用改代码
任务监控 失败告警、执行日志
手动触发 控制台一键触发
分片执行 任务按机器分片并行处理
高可用 调度中心挂一台不影响

二、XXL-Job 架构

2.1 两个核心组件

调度中心(xxl-job-admin):管理任务、触发调度
执行器(xxl-job-executor):业务项目里,接收调度执行任务

流程:
调度中心按 cron 触发 → 通知执行器
→ 执行器执行任务方法 → 上报执行结果
→ 调度中心展示日志/结果

2.2 架构特点

– 调度中心与执行器解耦:执行器是业务项目(Spring Boot 应用)
– 执行器自动注册:启动时注册到调度中心
– 调度中心高可用:集群部署(DB 存储调度信息)
– 执行结果回调:执行器上报日志和结果


三、部署调度中心

3.1 数据库初始化

— 调度中心需要数据库(xxl_job 库)
— 脚本:xxl-job 官方 doc/db/tables_xxl_job.sql
CREATE DATABASE xxl_job;
USE xxl_job;
— 执行官方 SQL(建 xxl_job_info、xxl_job_log 等表)

3.2 Docker 部署

docker run -d \\
–name xxl-job-admin \\
-p 8080:8080 \\
-e PARAMS="–spring.datasource.url=jdbc:mysql://host.docker.internal:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8 \\
–spring.datasource.username=root \\
–spring.datasource.password=123456"
\\
xuxueli/xxl-job-admin:2.4.0

# 访问 http://localhost:8080/xxl-job-admin
# 默认账号:admin / 123456

3.3 调度中心配置要点

# application.properties(调度中心)
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/xxl_job?…
# 执行器回调 token(校验)
xxl.job.accessToken=your–token
# 调度线程数
xxl.job.triggerpool.fast.max=200


四、接入执行器(业务项目)

4.1 依赖与配置

<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.4.0</version>
</dependency>

# 业务项目 application.yml
xxl:
job:
admin:
addresses: http://localhost:8080/xxl–job–admin # 调度中心
accessToken: your–token # 与调度中心一致
executor:
appname: order–executor # 执行器名称(注册名)
address: "" # 空则自动注册
ip: ""
port: 9999 # 执行器通信端口
logpath: /data/logs/xxl–job/jobhandler/ # 日志目录
logretentiondays: 30 # 日志保留天数

4.2 执行器配置类

@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;

@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
XxlJobSpringExecutor executor = new XxlJobSpringExecutor();
executor.setAdminAddresses(adminAddresses);
executor.setAppname(appname);
executor.setPort(port);
executor.setAccessToken(accessToken);
return executor;
}
}

4.3 第一个任务(Bean 模式)

@Component
public class OrderJobHandler {

@XxlJob("orderDailyReport")
public void dailyReport() {
// 业务逻辑:每日订单报表
XxlJobHelper.log("开始生成每日订单报表");
try {
List<Order> orders = orderMapper.selectYesterday();
reportService.generate(orders);
XxlJobHelper.log("报表生成完成,共 {} 单", orders.size());
} catch (Exception e) {
XxlJobHelper.log("报表生成失败: {}", e.getMessage());
// 抛异常 → 任务标记失败 → 触发失败告警
throw new RuntimeException(e);
}
}
}

关键:

  • @XxlJob("任务名") 注解标记任务处理器;
  • XxlJobHelper.log 写任务日志(调度中心可见);
  • 抛异常 = 任务失败(调度中心记录 + 告警);
  • 任务名要在调度中心"新增任务"时填一致。

五、任务配置(控制台)

5.1 新增任务

调度中心 → 任务管理 → 新增任务

基础配置:
– 执行器:order-executor(选注册好的执行器)
– 任务描述:每日订单报表
– 调度类型:CRON
– Cron:0 0 2 * * ? (每天凌晨 2 点)
– 运行模式:BEAN
– JobHandler:orderDailyReport(和 @XxlJob 名一致)

高级配置:
– 路由策略:分片广播 / 轮询 / 故障转移
– 调度过期策略:忽略 / 立即执行一次
– 阻塞处理策略:单机串行 / 丢弃后续调度 / 覆盖之前调度
– 任务超时时间:600 秒
– 失败重试次数:3 次

5.2 调度类型

类型说明
CRON 标准 cron 表达式(最常用)
固定速度 固定间隔(秒)
固定延迟 上次执行完多久后再执行

5.3 阻塞处理策略(必懂)

单机串行:上一次没执行完,新调度排队(默认)
丢弃后续调度:上一次没执行完,新调度直接丢弃
覆盖之前调度:强制覆盖(会杀旧线程,慎用)

选型:任务执行时长不确定 → 单机串行;宁可丢不可堆积 → 丢弃。


六、路由策略(集群执行)

6.1 场景

执行器部署了 3 台(order-executor ×3)
任务触发时,调度中心选哪台执行?

6.2 路由策略对比

策略行为适用
第一个 固定第一台 单点任务
轮询 轮流选 负载均衡
随机 随机选 简单
一致性 HASH 按参数哈希 参数绑定机器
最不经常使用 选最闲 通用
故障转移 失败换下一台 可靠性
分片广播 所有机器都执行 数据分片

6.3 分片广播(重点)

场景:100 万用户要发推送,3 台机器
分片广播:3 台都执行,每台处理 1/3(分片)

每台拿到:
– 分片参数:index(第几台)、total(共几台)
– 各台按 index 取自己的数据段

@Component
public class PushJobHandler {

@XxlJob("pushBatch")
public void pushBatch() {
// 分片参数(调度中心分片广播时传入)
int shardIndex = XxlJobHelper.getShardIndex(); // 0,1,2
int shardTotal = XxlJobHelper.getShardTotal(); // 3

XxlJobHelper.log("分片 {}/{} 开始", shardIndex + 1, shardTotal);

// 按分片取数据:user_id % total == index
List<Long> userIds = userMapper.selectByShard(
shardTotal, shardIndex, 10000);
for (Long uid : userIds) {
pushService.push(uid);
}
XxlJobHelper.log("分片 {}/{} 处理 {} 条完成",
shardIndex + 1, shardTotal, userIds.size());
}
}

— 分片取数 SQL(user_id 对 total 取模)
SELECT id FROM user WHERE MOD(id, #{total}) = #{index} LIMIT 10000;

分片的好处:3 台并行各处理 1/3 → 总耗时 = 单台的 1/3;挂一台其他照常(部分失败可重跑该分片)。


七、任务生命周期管理

7.1 生命周期状态

调度中心任务状态:
– 启用/停用:控制台一键切换(不用改代码)
– 调度日志:每次执行记录(成功/失败/耗时)
– 执行日志:XxlJobHelper.log 的明细日志

运维场景:
– 大促前暂停某任务
– 补跑:手动触发一次(执行一次)
– 失败任务:看日志 → 修复 → 重跑

7.2 失败处理

// 重试:调度中心配置失败重试次数(3 次)
// 告警:失败邮件/钉钉通知

// 代码里主动标记失败
@XxlJob("syncData")
public void syncData() {
try {
syncService.sync();
} catch (Exception e) {
XxlJobHelper.log("同步失败: {}", e.getMessage());
XxlJobHelper.handleFail("同步失败,请人工检查");
}
}

7.3 动态参数

// 任务可以带参数(控制台填)
@XxlJob("refreshCache")
public void refreshCache() {
// 控制台填的参数(JSON)
String param = XxlJobHelper.getJobParam();
// 例如:{"env":"prod","key":"hot_products"}
RefreshParam p = JSON.parseObject(param, RefreshParam.class);
cacheService.refresh(p.getKey());
}


八、实战:完整任务案例

8.1 场景:每日数据同步 + 报表 + 推送

@Component
public class DailyJobHandlers {

// 1. 凌晨 1 点:从业务库同步到数仓(分片广播)
@XxlJob("dataSync")
public void dataSync() {
int index = XxlJobHelper.getShardIndex();
int total = XxlJobHelper.getShardTotal();
int count = syncService.syncShard(total, index);
XxlJobHelper.log("同步 {} 条", count);
}

// 2. 凌晨 2 点:生成报表(单台,轮询路由)
@XxlJob("dailyReport")
public void dailyReport() {
reportService.generateYesterday();
XxlJobHelper.log("报表生成完成");
}

// 3. 早上 9 点:推送用户(分片广播)
@XxlJob("morningPush")
public void morningPush() {
int index = XxlJobHelper.getShardIndex();
int total = XxlJobHelper.getShardTotal();
pushService.pushDaily(index, total);
}

// 4. 手动任务:清理过期缓存(控制台触发)
@XxlJob("clearExpired")
public void clearExpired() {
int cleared = cacheService.clearExpired();
XxlJobHelper.log("清理 {} 个过期缓存", cleared);
}
}

调度中心配置:
1. dataSync cron: 0 0 1 * * ? 路由: 分片广播
2. dailyReport cron: 0 0 2 * * ? 路由: 轮询
3. morningPush cron: 0 0 9 * * ? 路由: 分片广播
4. clearExpired 手动触发 路由: 第一个


九、XXL-Job vs Quartz vs Spring @Scheduled

维度XXL-JobQuartz@Scheduled
分布式 支持(执行器集群) 需自己配 不支持(重复执行)
管理界面 有(完整) 有 无
动态调度 控制台改 可改(麻烦) 改代码
分片 支持 无 无
日志 调度中心查看 需集成 无
告警 内置 需集成 无
上手 简单 复杂 最简单

结论:

  • 单机简单任务 → @Scheduled;
  • 分布式/需要管理 → XXL-Job(首选);
  • 已有 Quartz 存量 → 评估迁移。

十、常见坑速查

现象原因解决
任务重复执行 没上调度框架/多实例 XXL-Job(自动避免)
执行器注册不上 token/端口/网络 检查配置与防火墙
JobHandler 找不到 任务名不一致 对名字
分片不均衡 取数方式错 MOD 取模
任务堆积 阻塞策略错 单机串行/丢弃
日志看不到 logpath 权限 目录可写
调度中心连不上 DB 连不上 检查数据库
时区差异 cron 时区 统一 +08:00

本章小结

XXL-Job 的实战主线:部署调度中心 → 业务项目接入执行器 → 控制台配任务(cron/路由/阻塞策略)→ 写 @XxlJob 处理器 → 分片广播处理大数据量 → 日志与告警闭环。核心心法:先想清楚"多实例不重复"(调度中心管),再想"大数据量怎么并行"(分片广播),最后配好"失败怎么办"(重试+告警)。定时任务从"没人管的小脚本"变成"可控可查可告警的正式组件",XXL-Job 是最省心的一条路。

下一篇讲 SkyWalking——分布式链路追踪。

(全文完)

赞(0)
未经允许不得转载:171主机测评 » XXLJob分布式定时任务实战指南
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址