动态资源分配与作业调度:多租户集群的资源博弈
上一篇解决了 Spark 在 YARN 和 K8s 上的部署模式。这一篇进入资源调度——在多个作业共享集群时,Spark 如何动态分配和回收资源。
默认情况下,一个 Spark 应用在启动时申请固定数量的 Executor,作业结束前一直占用。这在单用户开发环境下可以接受,在多租户生产集群中会造成严重的资源浪费——一个空闲的 Spark 应用占着 100 个 Executor 不释放,其他作业排队等待。
本文只抓一个问题:动态资源分配如何按需申请和释放 Executor,以及 FAIR 调度器如何在多个作业之间公平分配资源。
动态资源分配(DRA)
Dynamic Resource Allocation(DRA)允许 Spark 应用根据工作负载动态增减 Executor 数量:有 pending Task 时申请新 Executor,Executor 空闲超时后释放。
1 | |
DRA 的核心配置:
1 | |
DRA 的扩容策略是指数增长:第一次申请 1 个 Executor,如果仍有 pending Task,第二次申请 2 个,第三次 4 个,以此类推。这既避免了一次申请大量 Executor 造成资源浪费,又能在持续高负载时快速扩容。
External Shuffle Service
DRA 有一个前置条件:必须部署 External Shuffle Service(ESS)。
原因是:当一个 Executor 空闲被释放时,它本地磁盘上的 Shuffle 文件也随之消失。如果下游 Stage 还需要读取这些 Shuffle 数据,就会触发 FetchFailedException,导致上游 Stage 重算。
ESS 把 Shuffle 文件的管理从 Executor 进程中分离出来。ESS 是一个独立的服务进程,运行在每个集群节点上。Executor 写出的 Shuffle 文件由 ESS 管理,即使 Executor 进程退出,Shuffle 文件仍然可以通过 ESS 读取。
1 | |
在 YARN 上,ESS 作为 YARN NodeManager 的 Auxiliary Service 部署(yarn.nodemanager.aux-services=spark_shuffle)。在 K8s 上,ESS 通常以 DaemonSet 形式部署到每个节点。
作业调度器:FIFO vs FAIR
当一个 SparkContext 中有多个 Job 同时运行(比如不同线程提交的多个 action),内部调度器决定 Job 之间的资源分配策略。
FIFO 调度器(默认):先提交的 Job 优先获得所有资源。后提交的 Job 必须等前一个 Job 的所有 Task 完成后才能开始。简单但不公平——一个大 Job 会阻塞所有后续小 Job。
FAIR 调度器:所有 Job 轮流获得资源。多个 Job 的 Task 交替执行,每个 Job 都能获得部分资源。适合交互式查询场景——一个大 ETL 作业不会阻塞临时的小查询。
1 | |
FAIR 调度器还支持调度池(Scheduler Pool),可以把不同类别的 Job 分到不同的池中,每个池有独立的权重和最小资源保证:
1 | |
多租户场景的资源隔离
在多租户集群上,资源隔离需要在两个层面实现:
集群层面(YARN / K8s):通过 YARN Queue 或 K8s Namespace + ResourceQuota 限制每个租户可以使用的总资源量。一个租户最多使用 500 核 CPU 和 2TB 内存。
应用层面(Spark 调度器):通过 FAIR 调度器和调度池,在单个 Spark 应用内部的多个 Job 之间公平分配资源。
1 | |
实验:观察 DRA 行为
1 | |
模式提炼
DRA 体现了弹性资源管理的通用模式:按需申请、用完释放、指数扩容、延迟缩容。
同样的模式在云计算中广泛使用:AWS Auto Scaling Group 的实例伸缩、K8s 的 HPA(Horizontal Pod Autoscaler)、Serverless 函数的冷启动和自动伸缩。核心挑战都是一样的——在响应速度(快速扩容)和资源效率(及时缩容)之间找平衡。
FAIR 调度器体现了公平分享模式(Fair Share Scheduling),起源于操作系统的进程调度。YARN 的 FairScheduler、Hadoop 的 CapacityScheduler、Linux 的 CFS(Completely Fair Scheduler)都使用相同的思想。
工程迁移表
| Spark 概念 | YARN 对应 | K8s 对应 | 操作系统对应 |
|---|---|---|---|
| DRA(动态 Executor 分配) | YARN Container 动态申请 | Pod HPA / 动态 Pod 创建 | 进程池的动态 fork/kill |
| External Shuffle Service | NodeManager Auxiliary Service | DaemonSet | 共享文件系统 |
| FAIR Scheduler | FairScheduler / CapacityScheduler | 无直接对应 | CFS(完全公平调度器) |
| 调度池 | YARN Queue | K8s Namespace + ResourceQuota | cgroup |
| executorIdleTimeout | Container 空闲回收 | Pod 空闲删除 | 进程超时回收 |
常见误解
误解一:“DRA 开启后不需要设置 maxExecutors”。如果不设 maxExecutors,一个大作业可能申请集群中所有可用资源,挤占其他作业。生产环境必须根据公平性要求设置上限。
误解二:“FAIR 调度器在作业之间分配 Executor”。FAIR 调度器分配的是 Task 执行时间片,不是 Executor。所有 Job 共享同一组 Executor,FAIR 调度器决定在这些 Executor 上优先执行哪个 Job 的 Task。
误解三:“DRA 和推测执行不能同时使用”。可以同时使用,但需要注意推测执行会创建额外的 Task,这可能触发 DRA 申请更多 Executor。如果集群资源紧张,两者同时开启可能加剧资源争抢。
练习
-
配置 DRA(minExecutors=1, maxExecutors=10),提交一个 100 分区的 Shuffle 作业。在 Spark UI 的 Executors 页面记录 Executor 数量随时间的变化曲线。
-
在同一个 SparkContext 中从两个线程同时提交两个作业(一个大一个小),分别在 FIFO 和 FAIR 模式下执行。对比小作业的完成时间。
-
在不部署 ESS 的情况下开启 DRA,提交一个两阶段 Shuffle 作业。观察第一阶段的 Executor 被释放后,第二阶段是否出现 FetchFailedException。
系列导航
| 序号 | 主题 | 状态 |
|---|---|---|
| 00 | 导读:为什么 Spark 的核心是一张 DAG | 已完成 |
| 01 | RDD:弹性分布式数据集的五大属性 | 已完成 |
| 02 | 宽依赖与窄依赖:Stage 是怎样划分出来的 | 已完成 |
| 03 | DAG Scheduler 与 Task Scheduler:从逻辑计划到物理执行 | 已完成 |
| 04 | Shuffle 机制:数据跨分区交换的代价与优化 | 已完成 |
| 05 | 内存管理与 Tungsten:堆外内存、序列化与代码生成 | 已完成 |
| 06 | 存储体系:Block Manager、广播变量与累加器 | 已完成 |
| 07 | 容错机制:Lineage、Checkpoint 与推测执行 | 已完成 |
| 08 | Spark SQL 与 Catalyst:从 SQL 文本到物理算子树 | 已完成 |
| 09 | DataFrame 与 Dataset:类型安全与性能的折中 | 已完成 |
| 10 | Adaptive Query Execution:运行时改写执行计划 | 已完成 |
| 11 | 数据源 API 与谓词下推:让存储层帮忙过滤 | 已完成 |
| 12 | Structured Streaming:把流当成无界表 | 已完成 |
| 13 | 水位线与状态管理:流处理的时间语义 | 已完成 |
| 14 | Spark on YARN / K8s:资源管理与部署模式 | 已完成 |
| 15 | 动态资源分配与作业调度:多租户集群的资源博弈 | 本篇 |
| 16 | 性能诊断:Spark UI、Event Log 与调优思路 | 下一篇 |
| 17 | Spark 与 Flink:两种计算引擎的设计选择 |
参考资料
- Apache Spark 官方文档:Dynamic Resource Allocation. https://spark.apache.org/docs/latest/job-scheduling.html#dynamic-resource-allocation
- Apache Spark 官方文档:Job Scheduling. https://spark.apache.org/docs/latest/job-scheduling.html
- Apache Spark 源码:
core/src/main/scala/org/apache/spark/ExecutorAllocationManager.scalahttps://github.com/apache/spark - YARN FairScheduler:https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/FairScheduler.html
