目标
设计一款可靠、易用、可扩展、轻量的通用分布式任务调度系统。暂且将此系统称为:fusion-scheduler,融合各类业务的调度系统之意。
- 可靠:采用分布式设计,避免任务丢失,支持对失败任务的自动或手动重试
- 易用:支持固定间隔、固定延迟、CRON 表达式、API 触发、工作流、MapReduce 等调度方式,满足各类业务需求
- 可扩展:支持动态添加、删除任务、动态修改任务配置,支持动态添加、删除任务执行器、动态修改任务执行器配置
- 轻量:占用资源少
概念
架构设计
fusion-scheduler 作为一款分布工调度系统,架构上采用 Multi-Scheduler + Multi-Worker 的架构设计。多个 Scheduler 节点用于任务调度控制,比如:根据分组获取任务列表、分发任务到 Worker 节点执行、……;而多个 Worker 节点是用于执行任务,Worker 节点可以部署在多台机器上以提供更高的并发任务执行能力。
对于 Scheduler 和 Worker,设计为一种节点角色,也既一个节点可以同时作为 Scheduler 和 Worker 节点运行,比如在本机开发时只需要启动一个服务即可拥有所有功能。而在线上环境,可以根据负载和作业执行时需要的特殊性来部署合适数量的 Scheduler 和 Worker 节点。
fusion-scheduler 采用最小化设计原则,不依赖第三方中间件,采用 Rust 语言和 PostgreSQL(以后简称:PG)数据库开发。PG 将作为集群选主、任务定义管理、任务执行状态等集群状态数据存储库。
TODO
集群管理
集群节点有两种角色:Scheduler 和 Job Worker,
Scheduler
Scheduler 角色的节点负责调度作业任务管理,任务分发到 Worker 节点执行。Scheduler 角色节点的主要简化执行流程可如下描述:
SchedulerScheduler根据namespace获取所有在指定时间前需要触发的TriggerDefinition(触发器定义)- 通过
TriggerDefinition及关联的ProcessDefinition(流程定义)生成ProcessInstance(流程实例),并由一个 时间轮 来定时触发流程实例执行 - 当
ProcessInstance被触发时根据流程中定义的Task生成一个或多个TaskJob,并由SchedulerScheduler将其分发到某一个SchedulerWorker节点执行
Job Worker
Job Worker 角色的节点负责接收作业任务并执行(由 SchedulerWorker 实现)。一个 SchedulerWorker 必须与 Scheduler 通信,除此外并无其它限制。可以选择将 Worker 作为微服务来实现,也可以作为经典3层架构应用程序的一部分、或作为 Lamdba 函数来实现、或通过命令行工具等来编写。Worker 角色节点的主要简化执行流程可如下描述:
SchedulerWorker根据namespace注册自身到相应的 Scheduler 角色节点(SchedulerScheduler)- Worker 注册成功后,通过一个 gRPC 服务端流 API 等待接收 Job 任务
- 收到一个 Job 后执行,并将执行状态、执行结果及时送回 Scheduler 角色节点(
SchedulerScheduler)
流程(任务)
流程概念
流程定义: ProcessDefinition
fusion-scheduler 管理的单位是流程(ProcessDefinition),描述一个流程的执行规则,包括流程名称、执行参数、执行数据、标签等。每个流程可以由多个任务(Task)组成,任务依赖由 DAG 进行管理。
任务: Task
流程中实际执行的任务,它可能是一段 Rust/Java/Python/Shell/SQL 代码,也可能是一个 HTTP API 调用等,由 TaskType 来决定。
作业: TaskJob
任务作业。Task 的一次实际执行,当任务失败或因其它原因被重试时也会生成一个 TaskJob。TaskJob 将由(被调度到)Job Worker 节点执行。
触发器定义: TriggerDefinition
触发器定义。描述一个任务触发规则,包括触发方式、触发参数、……
触发器定义通常关联到一个 ProcessDefinition,当触发器定义被触发时,会生成一个 ProcessInstance。
流程实例: ProcessInstance
流程实例。描述一个流程的运行状态,包括流程开始时间、结束时间、执行状态、执行参数、执行数据、……一个流程实例可以由关联的 TriggerDefinition 触发执行,也可以通过 API 编程触发执行
流程与任务
可以看到,Task 更像传统的作业调度系统的概念,而 ProcessDefinition 更像 BPMN 中的一个业务流程(当一个流程只有一个任务时,就类似于传统的作业调度系统,比如:Quartz 的 Job)。这是 fusion-scheduler 分布式通用任务调度系统架构设计的核心。它既可用于定义任务作业,也可以用于编排业务流程微服务,还可以用于工作流和审批流调度。
技术架构
技术上,fusion-scheduler 采用了 Rust 语言和 PostgreSQL 作为主要开发语言和数据库。计划用到的主要库和框架有:
- tokio: 异步执行库
- axum/tower/hyper: HTTP 服务框架
- tonic/prost/nice-grpc-web(gRPC-Web 客户端库): gRPC 通信
- sqlx/sea-query: 数据库访问
- react/ant.desigin: 前端框架和 UI 库
- PostgreSQL: 数据存储、集群状态
- sqlite: 计划用于 Worker 节点本地缓存和持久化数据(日志)存储
Scheduler
Scheduler 时序图
- Scheduler init
- register to pg sched_scheduler
- 尝试向 sched_namespace 表感兴趣的 namespace 写入自己的 scheduler_id
- 成功则继续
- 失败则说明相关 Scheduler 节点已经存在,则将当前 Scheduler 转为容错备用模式,等待尝试重新关联
- 遍历有效的 trigger_definition,计算并生成 process_instance
- 遍历有效的 process_instance,生成 process_task
- 遍历待执行的 process_task,生成 task_job
- 分发 task_job 到相关的 job_worker
高可用
Scheduler 采用 PostgreSQL 作为集群状态数据存储库,当 Scheduler 启动后将向 sched_scheduler 表写入一条记录,并注册到集群中。当 Scheduler 节点重启时,会尝试向 sched_scheduler 表中写入一个记录,如果写入失败,则说明当前 Scheduler 节点已经存在,则将当前 Scheduler 节点标记为下线,并尝试向 sched_scheduler 表中写入一个记录,如果写入失败,则说明当前 Scheduler节点已经下线,则将当前 Scheduler 节点标记为下线。
按 namespace 隔离任务
当集群中只有一个 Scheduler 节点时,SchedulerScheduler 将关联所有 namespace 的任务。