Skip to content

单机任务调度 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││   只有一个能抢到                      │
│   │ └───┘└───┘└───┘│                                         │
│   └───────────────┘                                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

工作流程

  1. 调度中心根据配置的策略,选择一个执行器
  2. 调度中心向选中的执行器发送执行命令
  3. 执行器执行任务,完成后回调调度中心

方案二:去中心化 + ZooKeeper 协调

这是 ElasticJob-Lite 采用的方案。

┌─────────────────────────────────────────────────────────────┐
│                     去中心化模式                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌───────────────┐  ┌───────────────┐  ┌───────────────┐   │
│   │   App Server1 │  │   App Server2 │  │   App Server3 │   │
│   │ ┌───────────┐ │  │ ┌───────────┐ │  │ ┌───────────┐ │   │
│   │ │ Scheduler │ │  │ │ Scheduler │ │  │ │ Scheduler │ │   │
│   │ └─────┬─────┘ │  │ └─────┬─────┘ │  │ └─────┬─────┘ │   │
│   │       │       │  │       │       │  │       │       │   │
│   │       │ ZK    │  │       │ ZK    │  │       │ ZK    │   │
│   │       ▼       │  │       ▼       │  │       ▼       │   │
│   │ ┌───────────┐ │  │ ┌───────────┐ │  │ ┌───────────┐ │   │
│   │ │ Registry  │ │  │ │ Registry  │ │  │ │ Registry  │ │   │
│   │ │  客户端    │ │  │ │  客户端    │ │  │ │  客户端    │ │   │
│   │ └───────────┘ │  │ └───────────┘ │  │ └───────────┘ │   │
│   └───────┬───────┘  └───────┬───────┘  └───────┬───────┘   │
│           └──────────────────┼──────────────────┘           │
│                              ▼                               │
│                    ┌─────────────────┐                       │
│                    │   ZooKeeper    │                       │
│                    │  (协调服务)      │                       │
│                    │ ·选举主节点      │                       │
│                    │ ·分片分配        │                       │
│                    │ ·故障检测        │                       │
│                    └─────────────────┘                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

工作流程

  1. 每个应用服务器都内置调度器
  2. 通过 ZooKeeper 进行主节点选举
  3. 主节点负责任务分配,分片后分发到各个节点
  4. 如果主节点宕机,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                                   │
│   }                                                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

什么场景用单机?什么场景用分布式?

场景推荐方案原因
单机应用,数据量小单机调度简单,够用
多实例部署,数据量小分布式(单机执行模式)避免重复执行
多实例部署,数据量大分布式(分片执行模式)充分利用集群资源
需要统一管理监控分布式(调度中心模式)集中管控
要求高可用,无单点分布式(去中心化模式)无中心节点

总结

单机调度简单但有局限,分布式调度复杂但能力更强。

选择的关键是:你的任务是否需要在多台服务器上只执行一次?

  • 是 → 必须用分布式调度
  • 否 → 可以考虑单机调度,但也要预留扩展空间

思考题

分布式任务调度中,如果某个执行节点在执行任务时突然宕机,调度中心如何感知?已执行的任务如何处理?

这个问题的答案,涉及到分布式调度的容错机制设计。

基于 VitePress 构建