一、调度系统概述:增量计算背景、调度核心概念、系统设计目标
1.1 为什么需要增量计算?
先聊聊背景。我入行那会儿,大数据任务调度基本都是「全量计算」——每天凌晨把昨天的数据全部重跑一遍。听起来简单粗暴,对吧?但数据量一旦上去,问题就来了。
举个例子。你有一个用户画像系统,每天处理 10 亿条行为日志。全量跑一次,耗时 6 小时。如果只新增了 500 万条数据,你还得等 6 小时才能看到结果。这合理吗?
说白了,全量计算的核心痛点是:重复劳动太多。每次计算都把历史数据重新算一遍,而大部分数据根本没变化。
增量计算的核心思想:只处理变化的数据,保留历史计算结果,通过「变更 + 合并」的方式产出最新结果。
我在项目中遇到过这样一个场景:某电商平台的实时推荐系统,每天凌晨全量跑一次用户偏好模型,耗时 4 小时。但用户点击行为是实时的,等 4 小时再更新推荐结果,用户早就走了。后来我们改成增量调度——用户每产生一次点击,只更新该用户的偏好向量,整个系统延迟从 4 小时降到了 30 秒以内。
你想想看,增量计算本质上是在「算力」和「时效性」之间找平衡。不是所有场景都需要增量,但一旦数据量超过某个阈值,全量计算就扛不住了。
1.2 调度核心概念
聊调度之前,得先搞清楚几个基础概念。嗯,这里我按自己的理解来梳理一下。
1.2.1 任务(Task)
调度系统的最小执行单元。一个任务可以是一个 SQL 查询、一个 Spark 作业、一个 Python 脚本,甚至是一个 Shell 命令。
在增量场景下,任务通常分为两类:
- 全量任务:首次运行或数据重建时使用,处理全部数据
- 增量任务:日常运行,只处理变更数据
1.2.2 依赖(Dependency)
任务之间的上下游关系。比如任务 B 需要等任务 A 完成后才能启动。依赖关系决定了调度的执行顺序。
我记得有一次,团队里有人把依赖配反了——下游任务先跑,上游任务后跑。结果下游任务读到的全是旧数据,整个报表错了一整天。从那以后,我养成了一个习惯:依赖关系必须显式声明,并且要有循环检测。
1.2.3 触发(Trigger)
什么时候启动一个任务?常见触发方式有:
| 触发类型 | 说明 | 适用场景 |
|---|---|---|
| 时间触发 | 按固定时间间隔或 Cron 表达式触发 | 定时报表、离线批处理 |
| 事件触发 | 上游任务完成、数据到达、消息通知等 | 实时流处理、增量计算 |
| 手动触发 | 人工干预启动 | 调试、数据修复 |
1.2.4 状态(State)
每个任务在生命周期中会经历多个状态。我习惯把状态机画成一张图,方便团队理解:
个人经验:状态机设计时,一定要考虑「人工干预」的入口。比如任务卡在 RUNNING 状态超过 30 分钟,应该允许运维手动置为 FAILED 并重新调度。否则一旦状态卡死,整个链路就断了。
1.3 系统设计目标
好,背景和概念都聊完了。那一个增量计算调度系统,到底要设计成什么样?我总结下来,核心目标就四个。
1.3.1 高吞吐与低延迟
增量场景下,任务数量可能比全量多一个数量级。全量一天跑 1000 个任务,增量可能一天跑 10 万个。调度系统必须能扛住这个量级。
- 高吞吐:单位时间内能调度并执行的任务数
- 低延迟:从任务触发到实际启动的时间间隔
我曾经遇到过一个调度系统,任务触发后要等 5 秒才能分配到执行器。增量场景下,5 秒的延迟累积起来,整个链路就慢了。后来我们优化了任务队列和调度算法,把延迟降到了 200 毫秒以内。
1.3.2 精确一次语义(Exactly-Once)
增量计算最怕什么?数据重复或数据丢失。调度系统必须保证:
- 每个任务至少被执行一次(At-Least-Once)
- 每个任务最多被执行一次(At-Most-Once)
- 两者结合,就是 Exactly-Once
注意:Exactly-Once 在分布式环境下很难做到精确。实际工程中,我们通常通过「幂等性设计」来近似实现——任务重复执行不会产生副作用。
1.3.3 容错与自愈
分布式系统一定会出故障。调度系统必须能:
- 检测故障:任务超时、执行器宕机、网络分区
- 自动恢复:失败任务自动重试,或转移到其他执行器
- 人工兜底:提供手动重跑、跳过、暂停等运维能力
我记得有一次线上事故,某个执行器节点内存溢出,导致 200 个任务同时失败。幸好调度系统有自动重试机制,任务被重新分配到其他节点,最终全部成功。如果没有容错设计,那晚就得通宵加班了。
1.3.4 可观测性
调度系统不能是个黑盒。你需要知道:
| 观测维度 | 关键指标 | 工具 |
|---|---|---|
| 任务状态 | 成功率、失败率、重试次数 | Prometheus + Grafana |
| 执行延迟 | 调度延迟、执行耗时、排队时间 | 分布式追踪(Jaeger) |
| 资源使用 | CPU、内存、网络 IO | 容器监控(cAdvisor) |
| 数据质量 | 增量数据量、合并结果校验 | 自定义校验任务 |
我的建议:可观测性不是上线后才考虑的。设计阶段就要把 Metrics、Logs、Traces 三件套规划好。否则后期补,成本高得吓人。
1.4 增量调度 vs 全量调度
最后做个对比,帮你快速理解两者的差异:
| 维度 | 全量调度 | 增量调度 |
|---|---|---|
| 数据范围 | 全部数据 | 变更数据 |
| 执行频率 | 低(每天/每周) | 高(分钟级/秒级) |
| 任务数量 | 少 | 多 |
| 延迟要求 | 宽松(小时级) | 严格(秒级) |
| 容错难度 | 低(重跑即可) | 高(需保证 Exactly-Once) |
| 资源消耗 | 高 | 低 |
说白了,全量调度是「大力出奇迹」,增量调度是「四两拨千斤」。两者不是替代关系,而是互补关系。实际系统中,通常是全量 + 增量混合使用——首次用全量建立基线,后续用增量持续更新。
嗯,这一章就聊到这儿。核心就三件事:增量计算的背景、调度的核心概念、系统的设计目标。后面我们会一步步深入,把每个模块拆开来讲。