分布式任务调度:从 Cron 到 XXL-Job

“每天凌晨 3 点跑报表”——单机时一个 Cron 搞定。上集群后问题来了:3 个节点都执行,报表算 3 遍。分布式任务调度解决的就是:任务在集群里"只执行一次、可分片、可重试、可管理"。

一、单机 Cron 的局限

问 问 问 问 问 题 题 题 题 题 1 2 3 4 5 : : : : : 重 单 不 不 配 复 点 可 可 置 执 故 管 分 要 行 障 理 片 发 ( ( ( ( 版 集 执 跑 大 ( 群 行 没 任 改 节 跑 务 个 3 点 、 一 执 挂 失 个 行 个 了 败 节 时 节 没 点 间 点 → 有 扛 要 、 , 重 → 任 下 跑 新 务 次 几 部 任 丢 什 小 署 务 了 么 时 ) 跑 ) 时 ) 候 3 ) 遍 → 数 据 重 复 / 冲 突 )

二、分布式调度的核心机制

1. 调度器与执行器分离

调 执 调 调 度 行 度 度 中 器 中 中 心 ( 心 心 ( E 分 挂 S x 发 了 c e ? h c → → e u d t 执 高 u o 行 可 l r 器 用 e ) 执 部 r : 行 署 ) 管 ( : " → 多 管 任 调 " 务 结 度 任 怎 果 中 务 么 回 心 什 跑 传 , 么 " 同 时 ( 一 候 集 时 跑 群 刻 " 多 一 ( 台 个 一 ) 主 台 ) )

2. 执行策略(防重复)

单 分 失 机 片 败 路 广 转 由 播 移 : : : 任 任 执 务 务 行 指 分 失 定 成 败 一 台 N → 执 行 片 换 ( , 一 轮 每 台 询 台 重 / 执 试 哈 行 希 一 / 片 指 定 机 器 )

3. 任务分片(大任务的解法)

数 效 据 果 分 : 1 片 4 0 数 0 分 数 台 = 片 据 并 万 参 按 行 条 节 数 取 : 点 : 模 → 数 i 分 ( n 片 耗 4 d : 时 e W 台 x H / ) ( E 4 → 第 R 几 E 每 片 台 ) i 处 + d 理 t % 2 o 5 t 4 a 万 l = ( 总 0 片 ( 数 节 ) 点 1 ) . . .

三、XXL-Job 实践

XXL-Job 是国内最流行的开源方案(国人开发,文档全)。

架构

调 业 度 务 中 │ ▼ 任 心 务 ( 触 方 x 发 法 x ( l H - T j T o P b / - 嵌 a 入 d ) m i n ) → 注 册 / 心 跳 ← 执 行 器 ( 业 务 服 务 内 置 )

集成(Go)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 任务执行器:注册任务
func init() {
    exec := xxl.NewExecutor(
        xxl.ServerAddr("http://scheduler:8080/xxl-job-admin"),
        xxl.RegistryKey("delivery-job"),
    )
    exec.RegTask("report_daily", ReportDaily)   // 注册任务
    exec.Run()
}

// 任务方法(返回状态)
func ReportDaily(c *xxl.RunReq) string {
    // 分片处理:c.Index / c.Total
    processShard(c.Index, c.Total)
    return "success"
}

关键配置

调 任 度 务 中 配 路 失 超 阻 心 置 由 败 时 塞 : : 策 重 时 策 注 略 试 间 略 册 : : : : 执 第 重 任 串 行 一 试 务 行 器 个 次 超 / 、 / 数 时 丢 配 轮 熔 弃 询 断 后 C 续 r 分 报 / o 片 警 覆 n 广 盖 播 之 表 / 前 达 故 式 障 、 转 配 移 路 由 策 略

四、任务设计规范

✅ ✅ ✅ ✅ ✅ 任 任 大 任 关 务 务 任 务 键 要 要 务 要 任 幂 可 分 有 务 等 重 片 日 加 ( 入 ( 志 告 重 ( 单 ( 警 试 上 机 每 ( 不 次 超 片 失 重 没 每 败 复 跑 1 步 / ) 完 0 , 超 — , 失 时 — 这 分 败 / 前 次 钟 可 重 文 怎 查 试 《 么 → ) 后 接 办 仍 口 考 失 幂 → 虑 败 等 分 ) 设 阻 片 计 塞 ) 》 / 的 丢 通 弃 用 策 略 )

五、踩坑记录

  1. 任务不幂等:重试跑两遍 → 数据翻倍 → 任务必须幂等(用幂等键/状态机)
  2. 分片不均匀:按 ID 取模,数据分布不均 → 用业务维度分片(按用户/按区域)
  3. 执行器内存泄漏:任务里开 goroutine 不回收 → 执行器 OOM → 任务控制并发 + 超时
  4. 调度中心单点:admin 只部署一台 → 挂了任务全停 → admin 高可用(主备)
  5. 任务依赖:报表任务依赖数据同步任务完成 → 用"任务编排"(前驱/后继)或自检(数据就绪再算)

六、选型对比

方案 特点 适用
XXL-Job 功能全、文档全、中文 绝大多数团队
Quartz 老牌、Java 原生 Java 单机/简单集群
Elastic-Job 分片强(ZooKeeper) 大规模分片任务
自研(Redis 锁+Cron) 轻量 任务少、不想引框架
云服务 托管 不想运维

选型建议:任务 < 20 个且简单 → Redis 锁 + 本地 Cron 够用;任务多/要分片/要管理 → XXL-Job。

七、什么时候必须上调度平台

✅ ✅ ✅ ✅ ✅ 任 集 有 任 执 务 群 大 务 行 部 任 失 时 > 署 务 败 间 , 要 需 要 1 任 分 要 频 0 务 片 报 繁 会 警 调 个 重 / 整 , 复 重 ( 管 执 试 不 理 行 想 靠 发 猜 版 )

总结

分布式任务调度的核心认知:

  1. 调度与执行分离:调度中心管"何时",执行器管"如何"
  2. 三件事:防重复(路由策略)、可分片(大任务)、可恢复(重试转移)
  3. 任务本身要幂等:调度平台再稳,任务不幂等也白搭
  4. 运维三件套:日志、告警、超时熔断

定时任务是最容易"静默出错"的系统:没人盯着它,错了也是半夜错。有了调度平台 + 日志告警,任务才从"玄学"变成"可管理"。