2. 调度框架总体架构设计

在明确了调度框架要解决的问题之后,下一步必须把“系统长什么样”固定下来。总体架构设计不是画一张好看的框图,而是要在编码之前回答三个问题:系统分成哪几层、每层由哪些核心组件构成、组件之间通过什么数据和控制信号协作。这一节先给出一个可落地的分层架构,再逐层拆解组件职责,最后用数据流与控制流把各层串起来,并划定技术选型边界。

一、分层架构:从接口到执行的四层结构

调度框架采用四层结构,自上而下依次为接入层、调度层、执行层、资源层。分层的核心原则是“上层只依赖下层的抽象接口,不依赖具体实现”,这样替换执行器或存储时不会波及调度算法。

  • 接入层:对外提供任务提交、查询、暂停、取消等 API,负责参数校验、鉴权和限流。它不关心任务如何被调度,只把合法请求转成内部的任务描述对象。
  • 调度层:框架的大脑,包含任务注册表、触发器、调度队列和调度决策器。它决定“哪个任务、在什么时刻、交给哪个执行器”。
  • 执行层:由执行器池和任务运行器组成,负责真正拉起任务进程或线程、上报心跳、回传执行结果。
  • 资源层:提供持久化存储(任务定义、调度记录、执行日志)和运行时资源(线程池、连接池、分布式锁)。

四层之间通过明确定义的接口通信:接入层调用调度层的 submit(taskSpec),调度层通过 dispatch(taskId, executorId) 把任务交给执行层,执行层通过 report(result) 把状态写回资源层,调度层再从资源层读取状态做下一轮决策。这种“接口向下、数据向上”的约定,是后续所有模块解耦的基础。

接入层 任务提交 API 查询/取消 API 参数校验/鉴权 限流与请求转换 调度层 任务注册表 TaskRegistry 触发器 Cron / 事件 / 依赖 调度队列 优先级 / 延迟 调度决策器 Scheduler 执行层 执行器池 ExecutorPool 任务运行器 Runner 心跳上报 / 结果回传 资源层 任务定义存储 调度记录 / 执行日志 线程池 / 分布式锁 控制流:submit / dispatch 控制流:dispatch 控制流:持久化 数据流:状态回写 数据流:结果/心跳 数据流:查询响应 控制流(自上而下) 数据流(自下而上)

二、核心组件职责与协作边界

架构图给出了静态结构,但真正决定系统能否稳定运行的是每个组件的职责边界。以下按层说明关键组件“该做什么、不该做什么”。

任务注册表(TaskRegistry)是任务元数据的唯一权威来源,保存任务 ID、类型、触发规则、超时时间、重试策略、目标执行器标签等。它只负责“描述任务”,不负责“决定何时执行”。任务提交后先写入注册表,再由触发器读取。

触发器(Trigger)负责产生“任务该被调度了”的事件。常见触发器有三类:Cron 触发器按时间表达式产生事件;事件触发器监听外部消息或文件到达;依赖触发器等待上游任务成功。触发器只产生事件,不直接调用执行器,避免把调度决策逻辑散落到触发器中。

调度队列(DispatchQueue)是触发器与决策器之间的缓冲。它按优先级和就绪时间排序,支持延迟任务和重试任务入队。队列的存在让系统具备削峰能力:当大量任务同时到期时,决策器可以按自身吞吐逐步消费,而不是被瞬间压垮。

调度决策器(Scheduler)是控制流的核心。它从队列取出就绪任务,结合执行器负载、任务标签匹配、并发配额等条件,选择一个执行器并下发。决策器必须是无状态的,状态全部放在资源层,这样才能水平扩展多个调度实例。

执行器池(ExecutorPool)管理一组执行器实例,每个执行器有唯一 ID、标签、当前负载和心跳时间。执行器池负责健康检查,把失联执行器上的任务重新入队。

任务运行器(Runner)接收下发的任务,在本地线程池中执行,周期性上报心跳,执行结束后把结果和耗时写回资源层。运行器不参与调度决策,只忠实执行并如实上报。

三、数据流与控制流:两条方向相反的链路

把组件职责串起来看,系统里同时存在两条方向相反的链路。

控制流自上而下:接入层收到提交请求 → 写入任务注册表 → 触发器产生就绪事件 → 入调度队列 → 决策器选择执行器 → 下发到执行层 → 执行层持久化执行记录。控制流回答的是“命令往哪里走”。

数据流自下而上:执行层上报心跳和结果 → 写入资源层 → 调度层读取最新状态 → 接入层查询时返回给调用方。数据流回答的是“状态从哪里来”。

两条链路分离带来一个关键好处:调度决策不依赖执行层的同步返回。决策器下发任务后立即处理下一个任务,执行结果通过资源层异步回写。这样即使某个执行器响应慢,也不会阻塞整个调度循环。但代价是必须处理“下发成功但执行失败”的情况,因此执行层需要幂等执行和至少一次投递语义,调度层需要根据执行记录做超时补偿。

四、技术选型边界

架构确定后,技术选型就有了明确边界。以下给出一个可参考的选型基线,具体项目可按团队技术栈调整。

层次选型方向边界约束
接入层HTTP/REST 或 gRPC只做协议转换和校验,不写业务调度逻辑
调度层内存队列 + 关系型数据库持久化决策器无状态,状态必须外置
执行层线程池 / 进程池 / 容器化执行器执行器可水平扩展,通过标签匹配任务
资源层MySQL/PostgreSQL + Redis任务定义强一致,调度记录可最终一致
选型边界的三条硬约束:第一,调度层不得直接依赖执行层的具体实现,只能依赖执行器接口;第二,执行层不得反向调用调度层的内部方法,只能通过资源层回写状态;第三,任何跨层调用都必须经过接口,不允许跨层直接访问数据库表。

以任务提交为例,一次完整链路是:客户端调用 POST /tasks,接入层校验后生成 taskId 并写入注册表;触发器在下一个 Cron 周期产生就绪事件;决策器从队列取出任务,按标签选择执行器 exec-03 并下发;执行器运行任务,每 5 秒上报一次心跳,完成后写入 status=SUCCESS 和耗时;接入层查询时从资源层读取该记录返回。整条链路中,控制流向下、数据流向上,职责清晰,边界明确。