一、调度器架构演进:从单体到分布式的四代变迁
大家好,我是你们的讲师。今天咱们聊聊调度器架构的演进史。说实话,这个主题我讲了不下二十遍,但每次都有新感悟。你想想看,从最早的单体调度器到现在的分布式调度器,这中间走过的弯路,踩过的坑,都是真金白银换来的经验。
我个人习惯把调度器架构分成四代:单体调度器、两级调度器、共享状态调度器、分布式调度器。每一代都是为了解决上一代的问题而生。嗯,咱们一个一个来看。
核心观点:调度器架构的演进,本质上是「资源管理粒度」和「调度决策分布度」的博弈。越往后,系统越灵活,但复杂度也越高。
1.1 单体调度器(Monolithic Scheduler)
这是最原始的调度器形态。说白了,就是一个中央大脑,掌握所有资源信息,做出所有调度决策。
典型代表:Hadoop 1.x 的 JobTracker、Google Borg(早期版本)
核心特征:
- 一个调度器实例管理所有节点和任务
- 全局资源视图,决策简单直接
- 调度逻辑和状态存储都在同一个进程内
我在项目中遇到过这样的场景:早期用 Hadoop 1.x 跑离线任务,集群规模也就 200 台机器。JobTracker 单点故障,一挂全挂。更头疼的是,任务一多,JobTracker 的内存就飙到 8GB 以上,GC 停顿能把调度延迟拉到秒级。
我曾经踩过的坑:有一次线上集群的 JobTracker OOM,原因是某个用户提交了 10 万个 Map 任务,每个任务的状态都保存在内存里。恢复花了 40 分钟,业务方差点没把我电话打爆。从那以后,我对单体调度器的内存模型格外敏感。
单体调度器的优缺点:
| 优点 | 缺点 |
|---|---|
| 实现简单,逻辑清晰 | 单点瓶颈,扩展性差 |
| 全局最优调度决策 | 状态集中,故障影响面大 |
| 调试和运维方便 | 无法支持异构框架(如同时跑 Spark 和 Flink) |
单体调度器适合小规模集群(几百台以内)。一旦超过这个规模,你就得考虑升级了。
1.2 两级调度器(Two-Level Scheduler)
为了解决单体调度器的扩展性问题,业界提出了两级调度。核心思路是:中央调度器只做资源分配,具体任务调度交给各个框架自己的调度器。
典型代表:Apache Mesos、Hadoop YARN
工作流程:
- 中央调度器(如 Mesos Master)收集所有节点的资源信息
- 中央调度器向各个框架(如 Spark、Flink)的调度器发送「资源邀约」(Resource Offer)
- 框架调度器决定是否接受资源,以及如何分配任务
- 中央调度器根据框架的反馈,继续分配剩余资源
你想想看,这就像一个大房东(中央调度器)把房子租给不同的租户(框架),租户自己决定怎么装修、怎么住。大房东只关心房子租出去了没有,不关心里面怎么摆家具。
我的经验:Mesos 的两级调度在 2015 年左右非常火。我在某互联网公司做过 Mesos 集群,规模 3000 台。好处是 Spark 和 Flink 可以混部,资源利用率从 40% 提升到了 70%。但问题也很明显——框架调度器之间互相不知道对方的需求,容易产生资源碎片。
两级调度器的核心问题:
- 资源碎片:框架 A 拒绝了某些资源,框架 B 可能也拒绝,导致资源闲置
- 调度延迟:每次资源邀约都需要中央调度器和框架调度器来回交互
- 公平性难保证:中央调度器很难做到全局公平,因为不知道各个框架的真实需求
1.3 共享状态调度器(Shared-State Scheduler)
两级调度器的痛点在于「信息不对称」。共享状态调度器的思路是:让所有调度器共享一份资源状态,各自独立决策。
典型代表:Google Omega、Apache Aurora
核心机制:
- 所有调度器都访问同一个资源状态存储(通常是一个高性能的分布式存储,如 Spanner)
- 调度器通过乐观锁(Optimistic Concurrency Control)来避免冲突
- 如果两个调度器同时修改同一份资源状态,只有一个会成功,另一个重试
说白了,就是「大家都能看同一张地图,但谁先画上标记算谁的」。这比两级调度灵活多了,因为每个调度器都能看到全局资源视图。
关键区别:两级调度是「中央分配资源,框架决定任务」;共享状态调度是「框架自己看资源,自己决定任务,冲突了再解决」。
我在研究 Omega 论文时,印象最深的是它的调度延迟。Google 内部的数据显示,Omega 的调度延迟比 Borg(单体调度器)降低了 10 倍以上。为什么?因为去掉了中央调度器这个瓶颈,多个调度器可以并行工作。
共享状态调度器的挑战:
- 状态存储的性能:所有调度器都读写同一个存储,存储必须支持高并发和低延迟
- 冲突处理:高冲突场景下,重试次数增多,调度效率下降
- 一致性保证:需要强一致性还是最终一致性?这直接影响调度正确性
我曾经踩过的坑:在实现共享状态调度器时,我用了 etcd 作为状态存储。结果发现,当调度器数量超过 50 个时,etcd 的写入延迟从 2ms 飙升到了 50ms。后来换成 Raft 协议优化的分布式存储,才把延迟压下来。嗯,这里要注意:共享状态调度器的瓶颈往往不在调度逻辑,而在状态存储。
1.4 分布式调度器(Distributed Scheduler)
共享状态调度器虽然灵活,但状态存储仍然是中心化的。分布式调度器更进一步:没有中心化的调度器,也没有中心化的状态存储。每个节点自己调度,自己管理资源。
典型代表:Apache Spark 的 Standalone 模式(每个 Worker 自己调度)、Kubernetes 的 Scheduler Framework(可插拔调度器)、Google 的 Firmament
核心特征:
- 没有全局调度器,调度决策分散到各个节点
- 资源状态也分散存储,通常通过 Gossip 协议或分布式一致性协议同步
- 调度器之间通过消息或共享日志协作
你想想看,这就像一群人在一个大厅里找座位。没有管理员,每个人自己看哪里有空位,自己走过去坐下。如果有人同时看中同一个座位,就协商解决。
分布式调度器的优势:
- 无限扩展:理论上可以支持十万甚至百万台节点
- 高可用:没有单点故障,一个节点挂了不影响整体
- 低延迟:调度决策在本地完成,不需要远程通信
分布式调度器的挑战:
- 全局最优难保证:每个节点只看到局部信息,很难做出全局最优的调度决策
- 一致性维护复杂:节点间的状态同步需要额外的协议和开销
- 调试困难:问题可能出现在任何一个节点,排查起来非常痛苦
我的建议:如果你的集群规模在 5000 台以下,共享状态调度器可能是更好的选择。超过 5000 台,再考虑分布式调度器。不要为了「分布式」而分布式,架构选型要看实际需求。
四代调度器架构对比
| 架构类型 | 代表系统 | 扩展性 | 调度延迟 | 实现复杂度 | 适用规模 |
|---|---|---|---|---|---|
| 单体调度器 | Hadoop 1.x JobTracker | 低(几百台) | 低(毫秒级) | 低 | 小规模 |
| 两级调度器 | Mesos、YARN | 中(几千台) | 中(几十毫秒) | 中 | 中规模 |
| 共享状态调度器 | Omega、Aurora | 高(几万台) | 低(毫秒级) | 高 | 大规模 |
| 分布式调度器 | Firmament、Kubernetes | 极高(十万台+) | 极低(微秒级) | 极高 | 超大规模 |
架构演进的核心逻辑
说了这么多,我们来总结一下。调度器架构的演进,其实就三个驱动力:
- 规模驱动:集群越来越大,单体调度器扛不住了,所以有了两级调度
- 灵活性驱动:两级调度太死板,框架之间互相隔离,所以有了共享状态调度
- 去中心化驱动:共享状态调度还有中心化瓶颈,所以有了分布式调度
但要注意,每一代架构都不是完美的替代关系。我见过很多公司,几千台集群还在用 YARN(两级调度),跑得也挺好。架构选型没有银弹,适合你的才是最好的。
一句话总结:单体调度器是「一个人管所有事」,两级调度器是「房东管房,租户管装修」,共享状态调度器是「大家看同一张地图」,分布式调度器是「每个人自己找座位」。
知识体系图:调度器架构演进
这张图展示了四代调度器架构的演进脉络。从 2004 年的单体调度器,到 2016 年的分布式调度器,每代架构都在解决上一代的核心痛点。但要注意,这不是简单的替代关系——今天仍然有很多公司在用 YARN(两级调度),而且用得挺好。
好了,这一章的内容就到这里。调度器架构演进的核心逻辑,说白了就是「规模倒逼架构升级」。下一章我们会深入单体调度器的内部实现,看看 Borg 和 JobTracker 到底是怎么工作的。
想要更多资料,联系可可