大家好,我是晚安code。
这篇就把 XXL-JOB 的调度、任务分片、失败重试讲透,看完你也能上手。
一、单机定时任务的三个坑
单机定时任务扛不住微服务,不是代码写得差,是架构上就没法横向扩展。(本文基于 XXL-JOB v2.4.x 常用用法,2026 年 8 月验证,官方最新已到 3.x)
用 Spring 自带的 @Scheduled 跑定时任务,图省事。等业务拆成微服务、实例从 1 台变 6 台,坑一个个冒出来:
1)重复执行:6 个实例各跑一遍,对账任务把同一批订单处理了 6 次。
2)没法横向扩展:10 万条数据单节点要跑 40 分钟,加机器也帮不上忙,因为任务只落在其中一台。
3)失败没人知道:半夜节点 OOM 了,任务静悄悄消失,第二天看数据才发现漏了一批。


二、XXL-JOB 是什么:调度中心 + 执行器架构
XXL-JOB 把「什么时候执行」和「怎么执行」拆成两个独立角色,这是它能支撑集群扩缩容的根本。
XXL-JOB:许雪里开源的轻量级分布式任务调度平台(GitHub:https://github.com/xuxueli/xxl-job),把「什么时候执行」和「怎么执行」拆成两个角色。你可以理解为「给公司所有定时任务装一个统一指挥中心」。
调度中心(Scheduler Admin):XXL-JOB 的调度端,独立部署,提供 Web 界面管理任务、触发调度、失败重试和过期补偿,但不碰任何业务代码。你可以理解为「只负责发号施令的指挥部」。
执行器(Executor):打进业务服务里的一个 jar 包,负责接收调度、执行任务、回调结果,并自动向调度中心注册。你可以理解为「一线干活的执行部队」。
调度中心和执行器通过 HTTP 通信,互相不知道对方在哪台机器上——执行器启动后自己注册,调度中心按注册列表分发请求。所以执行器加一台、减一台,都不用改配置。
看图 1,整个链路就三条线:调度中心管任务、广播给执行器、执行器各自回调。


三、任务分片:10 万条数据怎么分给 N 台机器
任务分片广播是 XXL-JOB 处理海量数据并行最实用的一招,本质是「谁处理哪一份」由分片参数说了算。
任务分片广播:XXL-JOB 唯一会广播给所有执行器的路由策略,调度时把 shardIndex(当前分片序号)和 shardTotal(总分片数)一起发给每个执行器,大家按取模各处理一份数据。你可以理解为「一车货拆成 N 箱,每台车拉自己那一箱」。
比如 3 个执行器处理 10 万条用户数据:调度中心广播给全部 3 台,第 0 号处理 userId % 3 == 0 的数据,第 1 号处理 % 3 == 1 的,第 2 号处理 % 3 == 2 的,每台只跑三分之一,耗时直接砍到三分之一。新执行器上线,调度中心自动感知,下次调度 total 变成 4,数据自动重新切分,不用停机。
分片流程看图 2,注意每个执行器拿到的 index 不同。

分片 Handler 的写法也不难,核心就两行取参:
// 每天 0 点,把海量用户数据分给所有在线执行器
@XxlJob("shardingUserDataHandler")
public void shardingUserData() {
int index = XxlJobHelper.getShardIndex(); // 当前分片序号
int total = XxlJobHelper.getShardTotal(); // 总分片数
List<Long> userIds = queryUserIdsByShard(index, total);
for (Long userId : userIds) {
processUser(userId); // 只处理自己这一份
}
}


四、失败重试与调度策略:不是加了 cron 就完事
分布式定时任务的可靠性,一半靠失败重试,另一半靠调度策略兜底。
阻塞处理策略:任务还没跑完、下一次调度又来了时怎么办的规则。XXL-JOB 默认「单机串行」,还有「丢弃后续调度」和「覆盖之前调度」两种可选。你可以理解为「排队窗口:要么排队,要么直接作废下一次」。
失败重试这一块,XXL-JOB 给了几个可配的旋钮:
1)失败重试次数:任务管理页可配,默认 0。核心业务我建议设 3。
2)失败重试间隔:默认 30 秒,支持指数退避,比如 "30,60,120" 表示三次重试分别等 30、60、120 秒。
3)代码主动标失败:业务里用 XxlJobHelper.handleFail() 标记失败,调度中心检测到失败且还有重试次数,就会自动再触发。
我看过太多事故,都是「重试次数设成 0」埋的雷。任务一挂,日志里一片红,第二天数据对不上才发现。

路由策略这边,XXL-JOB 提供轮询、随机、一致性哈希、故障转移、忙碌转移等。故障转移我常用:某台执行器挂了,调度自动转到下一台,业务几乎无感。还有调度过期补偿:错过了调度时间,可以选忽略或立即补触发,避免「该跑没跑」。

可能有人会问:XXL-JOB 失败重试会不会把数据重复处理一遍?
会,如果任务不幂等。重试的代价就是可能重复执行,所以关键任务要么写幂等(比如按业务订单号去重),要么把重试次数控制住。我见过不幂等的对账任务被重试 3 次后账目乱了,排查了半天。
五、30 分钟接入实战
XXL-JOB 接入只要三步:加依赖、配置执行器、写 Handler,剩下交给调度中心界面。
1)加依赖,执行器核心就一个 jar:
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.4.1</version> <!-- 版本以官方最新为准,2.4.x 通用 -->
</dependency>
2)配执行器(application.yml):
xxl:
job:
admin:
addresses: http://调度中心IP:8080/xxl-job-admin
executor:
appname: order-job # 注册名,调度中心新增执行器时保持一致
port: 9999 # 执行器 HTTP 端口
logpath: /data/applogs/xxl-job
accessToken: # 与调度中心一致的令牌,可为空
3)写 Handler,一个注解就是一类任务:
@Component
public class OrderJobHandler {
// cron 方式:每天 0 点跑对账
@XxlJob("orderReconcileJobHandler")
public void reconcile() {
// 你的业务逻辑
XxlJobHelper.log("对账完成,共 {} 笔", count);
}
}
然后在调度中心 Web 界面「新增任务」,执行器选 order-job,填 Cron 或固定间隔,保存即生效。整个链路从依赖到跑通,基本就是这几步。
可能有人会问:XXL-JOB 调度中心要不要也部署集群?
要,生产环境建议至少两个调度中心实例,用数据库锁保证同一时刻只有一个在调度,另一个故障时顶上。我这边就是双节点部署,出现过一次主节点宕机,任务没断,靠的就是这个。
六、XXL-JOB vs Quartz vs @Scheduled,怎么选
选型不是越重越好:任务少就用轻的,任务多、要分片、要重试,再上 XXL-JOB。
| 方案 | 分布式调度 | 任务分片 | 失败重试 | 适合规模 |
|---|---|---|---|---|
| @Scheduled | 不支持 | 无 | 无 | 个位数任务 |
| Quartz | 需自己改造 | 无 | 弱 | 几十个任务 |
| XXL-JOB | 开箱即用 | 分片广播 | 次数+间隔 | 上百到上万个任务 |
项目里定时任务不超过 10 个、也不在意失败告警,@Scheduled 够用;一旦涉及多实例、海量数据、要分片,直接上 XXL-JOB,别自己造轮子。XXL-JOB 不是银弹,但在我这边,它是中小团队把分布式定时任务从「没人管」变成「平台管」的最轻方案,尤其是任务分片和失败重试这两块,省下的运维精力远超接入成本。
想深入可以看官方仓库和文档:
我是晚安code,持续分享编程干货。觉得有用的话记得点赞收藏和关注~也欢迎在评论区聊聊:你所在的项目用的是 XXL-JOB 还是别的定时任务框架,都踩过哪些坑?