3. 任务调度核心模型:四要素深度解析

任何 DAG 任务调度系统,无论其架构如何演进,都建立在四个核心抽象之上:任务(Task)、依赖(Dependency)、执行器(Executor)、调度器(Scheduler)。这四个要素构成了调度系统的“元模型”,决定了系统的能力边界与扩展方向。

理解四要素的本质及其相互关系,是设计高可用、可扩展调度系统的第一步。下面我们逐一拆解每个要素的职责、属性与设计要点。

3.1 任务(Task):最小执行单元

任务是调度系统中最基本的执行单元,它封装了一段可执行的逻辑。在 DAG 模型中,每个节点就是一个 Task。任务的粒度决定了系统的灵活性:粒度过粗会导致资源浪费,粒度过细则会增加调度开销。

属性维度 典型字段 说明
标识 taskId, name, version 全局唯一,用于追踪与重试
执行定义 command, image, scriptRef 描述“做什么”,可以是 Shell、Python、容器镜像
资源需求 cpu, memory, gpu, disk 调度器据此进行资源匹配与装箱
超时与重试 timeout, maxRetries, retryDelay 防止任务“死锁”或无限重试
状态机 PENDING → RUNNING → SUCCESS/FAILED 驱动任务生命周期流转
设计要点:任务定义应保持“无状态”和“幂等”。无状态意味着任务不依赖本地磁盘数据;幂等意味着重复执行相同任务不会产生副作用。这两点是实现失败重试和水平扩展的前提。

3.2 依赖(Dependency):DAG 的骨架

依赖定义了任务之间的执行顺序与数据流向。没有依赖,任务就是孤立的;有了依赖,任务才形成有向无环图。依赖管理是调度系统最核心的复杂度来源。

依赖通常分为两类:

  • 硬依赖(Hard Dependency):上游任务必须成功,下游任务才能启动。这是 DAG 调度中最常见的依赖类型。
  • 软依赖(Soft Dependency):上游任务的状态影响下游任务的启动条件,但不强制要求成功。例如“上游完成即可,无论成功或失败”。

依赖关系的表示方式直接影响调度器的判断效率:

  • 邻接表:每个任务记录其上游任务列表(predecessors)和下游任务列表(successors)。适合动态图遍历。
  • 入度计数:每个任务记录其尚未满足的上游依赖数量。入度归零时,任务即可被调度。这是拓扑排序的经典实现。
边界拆分原则:依赖管理应独立于任务执行逻辑。调度器只关心“依赖是否满足”,不关心任务内部做了什么。这种分离使得依赖检查可以提前进行,甚至可以在任务提交阶段就完成静态校验。

3.3 执行器(Executor):任务的“工人”

执行器是实际运行任务的组件。它从调度器接收任务指令,在指定的资源环境中执行任务,并将执行结果(状态、日志、输出)上报给调度器。

执行器的设计模式决定了系统的资源利用率和隔离性:

模式 资源模型 隔离性 典型场景
进程级 每个任务启动一个进程 低(共享宿主环境) 简单脚本、开发环境
容器级 每个任务运行在独立容器中 高(资源隔离) 生产环境、多租户场景
线程级 线程池复用 极低 轻量计算、IO 密集型
常见误区:将执行器与调度器混为一谈。调度器负责“决策”——决定哪个任务该运行;执行器负责“执行”——实际运行任务。两者职责分离后,调度器可以专注于全局最优调度策略,执行器可以专注于资源管理和任务生命周期。

3.4 调度器(Scheduler):决策中枢

调度器是整个系统的“大脑”。它维护 DAG 的全局状态,监听任务完成事件,触发依赖检查,决定下一个可调度任务,并将任务分发给合适的执行器。

调度器的核心职责可以概括为三个循环:

  1. 事件循环:监听任务状态变更(完成、失败、超时),更新 DAG 状态。
  2. 拓扑循环:遍历 DAG,检查入度归零的任务,生成可调度任务列表。
  3. 分发循环:根据资源约束和调度策略(如 FIFO、优先级、公平调度),将任务分配给执行器。

调度器的设计难点在于:

  • 并发控制:多个任务同时完成时,如何避免重复调度同一个下游任务?
  • 状态一致性:调度器宕机后,如何恢复 DAG 的中间状态?
  • 调度延迟:从任务完成到下游任务启动的时间间隔,应尽可能短。

3.5 四要素协作关系

下面这张图展示了四个要素在 DAG 调度系统中的协作流程:

DAG 调度四要素协作关系 Task 最小执行单元 Dependency DAG 骨架 Scheduler 决策中枢 Executor 任务工人 定义 输入 分发任务 上报状态 调度器不直接执行任务,执行器不决定调度顺序;依赖关系由调度器解析,任务定义由执行器消费。 四要素各司其职,边界清晰,系统才能灵活扩展。

3.6 边界拆分:为什么四要素必须分离?

很多初期的调度系统将四要素混在一起:任务定义中包含了调度逻辑,执行器里嵌入了依赖检查。这种“大泥球”架构在规模增长后迅速暴露出问题:

  • 无法独立扩展:任务量增加时,需要同时扩容调度器和执行器,资源浪费。
  • 难以故障隔离:执行器崩溃可能导致调度状态丢失。
  • 调度策略僵化:修改调度算法需要重新部署整个系统。

四要素分离后的架构优势:

要素 独立演进能力 故障影响范围
Task 新增任务类型无需修改调度器 单个任务失败不影响其他任务
Dependency 支持动态依赖、条件依赖等复杂模式 依赖定义错误仅影响本 DAG
Scheduler 可替换调度策略(FIFO/优先级/公平) 调度器宕机可通过 Leader 选举恢复
Executor 可对接不同资源池(K8s/YARN/本地) 单个 Executor 崩溃由调度器重新分发
架构启示:四要素的边界拆分,本质上是将“做什么”(Task)、“何时做”(Dependency)、“谁来做”(Executor)、“怎么做决策”(Scheduler)四个关注点解耦。这种拆分使得每个要素都可以独立优化、独立部署、独立扩展,是分布式系统“关注点分离”原则在调度领域的经典实践。