单机任务调度 vs 分布式任务调度
周一早上,你打开监控系统,发现上周刚上线的「每日报表生成」任务,在周一凌晨跑了整整两个小时。
排查后发现:集群有 4 台服务器,报表任务在每台服务器上都执行了一遍。
这就是单机任务调度和分布式任务调度的第一个核心差异——谁去执行任务。
单机任务调度的困境
1. 无法横向扩展
单机任务调度(4台服务器)
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ Server1│ │ Server2│ │ Server3│ │ Server4│
│ [任务] │ │ [任务] │ │ [任务] │ │ [任务] │
│ 执行! │ │ 执行! │ │ 执行! │ │ 执行! │
└────────┘ └────────┘ └────────┘ └────────┘
×4 重复执行!数据错乱!4 台服务器,任务执行 4 次。10 台服务器,执行 10 次。
这在某些场景下是灾难:
- 报表生成 → 生成 4 份报表,数据不一致
- 发送邮件 → 用户收到 4 封重复邮件
- 扣款操作 → 重复扣款,直接完蛋
2. 单点故障
┌────────────────────────────────────────┐
│ 单机调度的致命问题 │
├────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 任务调度器 │────▶│ 任务执行器 │ │
│ └────┬─────┘ └──────────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 调度器挂了 │ │ 任务全停! │ │
│ └──────────┘ └──────────┘ │
│ │
│ 凌晨 3 点,运维被叫醒... │
└────────────────────────────────────────┘如果调度器所在的服务器宕机,所有定时任务都停了,直到服务器恢复。
3. 资源利用率低
场景:每小时处理 100 万条数据
单机调度(1台 8核服务器):
- 所有任务共用 8 个线程
- 数据处理能力有限
- CPU 利用率可能只有 30%
分布式调度(4台 8核服务器):
- 每台服务器处理 25 万条数据
- 并行处理,4 倍效率
- CPU 利用率可以达到 80%分布式任务调度的解决方案
核心思想:分片 + 协调
┌─────────────────────────────────────────────────────────────┐
│ 分布式任务调度 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ 调度中心 │ │
│ │ (统一调度 + 协调) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Server1 │ │ Server2 │ │ Server3 │ │
│ │ [分片0] │ │ [分片1] │ │ [分片2] │ │
│ │ [分片3] │ │ [分片4] │ │ [分片5] │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘方案一:统一调度中心 + 执行器集群
这是 XXL-Job、Quartz 集群采用的方案。
┌─────────────────────────────────────────────────────────────┐
│ 调度中心模式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ │
│ │ 调度中心 │ ← 负责「什么时候执行」 │
│ │ (AdminSite) │ 不执行具体任务 │
│ └───────┬───────┘ │
│ │ 触发执行 │
│ ▼ │
│ ┌───────────────┐ │
│ │ 执行器集群 │ ← 负责「谁来执行」 │
│ │ ┌───┐┌───┐┌───┐│ 多个执行器抢任务, │
│ │ │ App││ App││ App││ 只有一个能抢到 │
│ │ └───┘└───┘└───┘│ │
│ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘工作流程:
- 调度中心根据配置的策略,选择一个执行器
- 调度中心向选中的执行器发送执行命令
- 执行器执行任务,完成后回调调度中心
方案二:去中心化 + ZooKeeper 协调
这是 ElasticJob-Lite 采用的方案。
┌─────────────────────────────────────────────────────────────┐
│ 去中心化模式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ App Server1 │ │ App Server2 │ │ App Server3 │ │
│ │ ┌───────────┐ │ │ ┌───────────┐ │ │ ┌───────────┐ │ │
│ │ │ Scheduler │ │ │ │ Scheduler │ │ │ │ Scheduler │ │ │
│ │ └─────┬─────┘ │ │ └─────┬─────┘ │ │ └─────┬─────┘ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ ZK │ │ │ ZK │ │ │ ZK │ │
│ │ ▼ │ │ ▼ │ │ ▼ │ │
│ │ ┌───────────┐ │ │ ┌───────────┐ │ │ ┌───────────┐ │ │
│ │ │ Registry │ │ │ │ Registry │ │ │ │ Registry │ │ │
│ │ │ 客户端 │ │ │ │ 客户端 │ │ │ │ 客户端 │ │ │
│ │ └───────────┘ │ │ └───────────┘ │ │ └───────────┘ │ │
│ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │
│ └──────────────────┼──────────────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ ZooKeeper │ │
│ │ (协调服务) │ │
│ │ ·选举主节点 │ │
│ │ ·分片分配 │ │
│ │ ·故障检测 │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘工作流程:
- 每个应用服务器都内置调度器
- 通过 ZooKeeper 进行主节点选举
- 主节点负责任务分配,分片后分发到各个节点
- 如果主节点宕机,ZooKeeper 重新选举
两种方案的对比
| 维度 | 调度中心模式 (XXL-Job) | 去中心化模式 (ElasticJob) |
|---|---|---|
| 架构复杂度 | 较高(需要维护调度中心) | 较低(无需额外服务) |
| 调度一致性 | 强(调度中心统一控制) | 依赖 ZooKeeper |
| 扩展性 | 好(增加执行器即可) | 好(增加节点即可) |
| 故障影响 | 调度中心挂了只影响调度 | 单节点挂了自动转移 |
| 适用场景 | 需要集中管理、任务量大 | 需要高可用、快速部署 |
分布式调度的三大挑战
1. 任务分片
如何把一个大任务拆分成多个小任务,分配到不同的服务器?
任务:处理 1000 万条数据
分片策略:
┌──────────────────────────────────────────────────┐
│ 分片参数: shardingItemParameters │
│ 0=1-250万, 1=250万-500万, 2=500万-750万, 3=750万-1000万 │
└──────────────────────────────────────────────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Server1 │ │ Server2 │ │ Server3 │ │ Server4 │
│ 处理分片0 │ │ 处理分片1 │ │ 处理分片2 │ │ 处理分片3 │
│ 1-250万 │ │ 250-500万 │ │ 500-750万 │ │ 750-1000万│
└──────────┘ └──────────┘ └──────────┘ └──────────┘2. 分布式锁
如何保证同一个任务只在一台服务器上执行?
分布式锁实现方式:
方案一:数据库唯一键
┌──────────────┐
│ INSERT INTO │
│ lock_table │
│ (task_id) │
│ VALUES (...) │
└──────────────┘
成功 → 获取锁
失败 → 已存在,跳过
方案二:Redis SETNX
SETNX lock:task1 node1
成功 → 获取锁
失败 → 已存在,跳过
方案三:ZooKeeper 临时节点
create /locks/task1/lock node1
成功 → 获取锁
失败(已存在)→ 监听变化3. 任务结果回调
执行完成后,如何通知调度中心?
┌─────────────────────────────────────────────────────────────┐
│ 执行结果回调 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 执行器 ──────────────────────────────────▶ 调度中心 │
│ │
│ POST /api/callback │
│ { │
│ "taskId": "xxx", │
│ "status": "SUCCESS", // SUCCESS / FAIL │
│ "message": "处理了1000条数据", │
│ "startTime": 1700000000, │
│ "endTime": 1700000100 │
│ } │
│ │
└─────────────────────────────────────────────────────────────┘什么场景用单机?什么场景用分布式?
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 单机应用,数据量小 | 单机调度 | 简单,够用 |
| 多实例部署,数据量小 | 分布式(单机执行模式) | 避免重复执行 |
| 多实例部署,数据量大 | 分布式(分片执行模式) | 充分利用集群资源 |
| 需要统一管理监控 | 分布式(调度中心模式) | 集中管控 |
| 要求高可用,无单点 | 分布式(去中心化模式) | 无中心节点 |
总结
单机调度简单但有局限,分布式调度复杂但能力更强。
选择的关键是:你的任务是否需要在多台服务器上只执行一次?
- 是 → 必须用分布式调度
- 否 → 可以考虑单机调度,但也要预留扩展空间
思考题
分布式任务调度中,如果某个执行节点在执行任务时突然宕机,调度中心如何感知?已执行的任务如何处理?
这个问题的答案,涉及到分布式调度的容错机制设计。
