2、核心概念:任务、作业、工作流、调度器、执行器
聊任务调度,很多人一上来就扎进代码里。我个人的习惯是,先把几个核心概念掰扯清楚。说白了,这五个词就是整个调度系统的“乐高积木”。你搞懂了它们各自长什么样、怎么拼,后面看什么框架都一通百通。
一句话总结:任务是最小执行单元,作业是任务的集合,工作流是作业的编排,调度器是大脑,执行器是手脚。
2.1 任务(Task)—— 最小执行单元
任务,就是你要让计算机干的一件具体的事。它不能再拆了,拆了就变味儿了。
举个例子:
- 执行一条 SQL 查询
- 运行一个 Shell 脚本
- 调用一个 HTTP 接口
- 处理一个文件
我在项目中见过最坑的事,就是把一个“下载 10GB 文件并解压再清洗”的逻辑写成一个任务。结果每次重跑都要从头下载,气得运维小哥直拍桌子。记住,任务要足够小、足够原子。
我的经验:一个任务最好能在 5 分钟内跑完。超过这个时间,你就该考虑拆分了。不是技术做不到,而是排查问题时你会感谢自己。
2.2 作业(Job)—— 任务的集合
作业,就是把一组相关的任务打包在一起。它定义了这些任务怎么跑:串行还是并行?失败后重试几次?超时时间是多少?
你想想看,一个数据仓库的 ETL 流程,通常包含:
- 抽取数据(Extract)
- 清洗数据(Clean)
- 转换格式(Transform)
- 加载到目标表(Load)
这四个步骤,每个都是一个任务。把它们组合成一个“每日数据同步作业”,就是作业干的事。
| 概念 | 粒度 | 是否可独立调度 | 典型例子 |
|---|---|---|---|
| 任务 | 最细 | 一般不单独调度 | 执行一条 SQL |
| 作业 | 中等 | 是 | 每日数据同步 |
2.3 工作流(Workflow)—— 作业的编排
工作流,说白了就是“谁先谁后”的问题。单个作业好办,但现实中的业务往往是多个作业互相依赖的。
比如:
- 作业 A(数据采集)必须先跑完
- 然后作业 B(数据清洗)和作业 C(数据聚合)可以并行跑
- 最后作业 D(生成报表)等 B 和 C 都跑完才能开始
这就是一个典型的工作流。我曾经接手过一个老系统,依赖关系写在 Shell 脚本的 sleep 里——等 5 分钟,觉得上游该跑完了。结果呢?数据对不上,排查了一整天才发现是时序问题。
避坑指南:工作流的依赖关系一定要显式声明,不要靠“时间窗口”来隐式保证。我曾经吃过这个亏,凌晨三点被电话叫醒去修数据管道,那种滋味……嗯,你懂的。
2.4 调度器(Scheduler)—— 大脑
调度器是整个系统的核心。它负责三件事:
- 什么时候跑 —— 基于时间表达式(Cron、时间间隔等)
- 跑什么 —— 触发哪个作业或工作流
- 跑完怎么办 —— 成功、失败、重试、告警
调度器不干活,它只发号施令。就像项目经理,自己不写代码,但知道谁该干什么、什么时候干。
常见的调度器有 Quartz、Airflow 的 Scheduler、XXL-JOB 的调度中心。它们的设计思路大同小异,核心就是一个“时间轮”或者“优先级队列”。
2.5 执行器(Executor)—— 手脚
执行器,就是真正干活的。它收到调度器的指令,拉起任务进程,监控运行状态,然后把结果汇报回去。
执行器通常部署在 worker 节点上。我习惯把执行器设计成无状态的——这样扩容特别方便,机器不够了直接加一台,调度器自动分配任务过去。
关键设计原则:调度器和执行器一定要解耦。调度器挂了,正在跑的任务不受影响;执行器挂了,调度器可以把任务分给其他执行器。这是高可用的基础。
2.6 它们之间的关系
下面这张图,把五个概念串起来了。我每次给团队新人讲调度系统,都会先画这张图。
2.7 实际项目中的分层设计
我参与过的一个金融级调度平台,就是按这五个概念来分层的:
- API 层:接收用户提交的作业定义和工作流 DAG
- 调度层:基于 Quartz 做时间触发,把就绪的作业扔进消息队列
- 执行层:Worker 进程从队列拉取任务,启动子进程执行
- 存储层:MySQL 存元数据,Redis 存实时状态
这种分层的好处是,每一层都可以独立扩展。调度层压力大了,加调度节点;执行层任务积压了,加 Worker。互不干扰。
一个小技巧:任务执行结果一定要有明确的“成功/失败/超时”状态,不要搞什么“部分成功”。我在代码里见过返回码是 0 但数据没写全的情况,排查起来特别痛苦。
2.8 常见误区
最后,说几个我踩过的坑:
- 把任务当作业用 —— 一个任务里塞了太多逻辑,失败重跑成本极高
- 工作流画成蜘蛛网 —— 依赖关系太复杂,一个节点挂了影响一大片
- 调度器和执行器混在一起 —— 调度器挂了,执行器也跟着挂,没有高可用
- 忽略任务幂等性 —— 同一个任务跑两次,数据翻倍了
嗯,这些概念看起来简单,但真正落地的时候,每一个都值得深挖。后面我们会逐个展开,看看它们在实际框架里是怎么实现的。