Spark 集群部署中的动态资源分配(Dynamic Resource Allocation, DRA),这是生产环境提升集群资源利用率和作业吞吐量的关键机制。
一、 动态资源分配的核心价值
- 痛点: 在静态资源配置下(
spark.executor.instances固定):- 资源浪费: Spark 应用在启动时会一次性申请所有 Executor,即使任务初期或末期不需要那么多资源(如小任务、任务收尾阶段),导致集群资源闲置。
- 资源不足: 对于长任务或数据量突增的任务,初始分配的 Executor 可能不够用,无法动态扩展,导致任务变慢甚至失败。
- 队列阻塞: 大应用长时间占用大量资源,阻塞了其他小应用的提交。
- 解决方案: 动态资源分配 (DRA)
- 按需伸缩: Spark 应用在运行期间根据当前负载(主要是待处理的 Task 数量)动态地申请(Scale Up)和释放(Scale Down)Executor 资源。
- 核心目标:
- 最大化集群资源利用率: 让空闲资源能被其他应用使用。
- 提升作业吞吐量: 集群可以同时运行更多应用。
- 应对负载波动: 自动适应任务不同阶段对资源的需求变化。
二、 动态资源分配的工作原理与关键组件
-
依赖基础:External Shuffle Service (ESS)
- 为什么必需? 这是 DRA 能释放 Executor 的前提!
- 问题: Executor 退出时,其持有的由 Map Task 产生的 Shuffle 数据也会消失。Reduce Task 后续需要这些数据时就会失败。
- 解决方案:ESS
- 一个长期运行在每个 Worker 节点上的独立服务(通常与 NodeManager 同节点)。
- Map Task 将 Shuffle 数据写入 ESS 管理的存储(通常是磁盘),而不是 Executor 的本地目录。
- Reduce Task 直接从 ESS 读取 Shuffle 数据。
- 关键好处: Executor 可以安全退出(即使它持有过 Map Task 的输出),因为 Shuffle 数据已由 ESS 托管。Reduce Task 后续拉取数据时只需找 ESS,不依赖特定的 Executor。
- 部署: 需要在集群所有 Worker 节点上部署并启动 ESS。YARN 模式下通常是
spark-<version>-yarn-shuffle.jar,Standalone 模式有对应的 shuffle service。
-
核心流程:申请与释放 Executor
- 角色:
- Spark Driver: 决策者。根据策略判断是否需要申请或移除 Executor。
- 集群管理器 (Cluster Manager): 资源提供者。YARN ResourceManager 或 Standalone Master。负责实际分配/回收容器(Container)或 Worker。
- External Shuffle Service (ESS): 保障者。确保 Shuffle 数据在 Executor 移除后依然可用。
- Executor 申请 (Scale Up):
- Spark Scheduler 检测到待调度(Pending)的 Task 数量积压。
- 积压量超过阈值(
spark.dynamicAllocation.schedulerBacklogTimeout触发后,且积压持续存在)。 - Driver 向 Cluster Manager 申请新的 Executor。
- Cluster Manager 在可用节点上启动新的 Container/Worker,并启动 Executor。
- 新 Executor 向 Driver 注册,开始领取 Task 执行。
- Executor 释放 (Scale Down):
- Executor 处于空闲状态(没有运行 Task,且其 Shuffle 数据已被 ESS 托管或不再需要)。
- 空闲时间持续超过设定的阈值(
spark.dynamicAllocation.executorIdleTimeout)。 - Driver 标记该 Executor 为可移除。
- Driver 通知 Cluster Manager 释放该 Executor 占用的资源(Container/Worker)。
- Executor 优雅退出。即使它曾持有 Map 输出,Reduce Task 后续也能通过 ESS 获取数据。
- Shuffle 数据生命周期管理:
- Driver 会跟踪每个 Executor 上产生的 Shuffle 数据(通过 BlockManager)。
- 当确定所有依赖于某 Executor 上 Map 输出的 Reduce Task 都已完成后,Driver 会通知 ESS 可以安全删除该 Executor 产生的过期 Shuffle 数据(通过
spark.dynamicAllocation.shuffleTracking.timeout控制保守删除)。
三、 关键配置参数详解 (spark-defaults.conf 或 spark-submit --conf)
-
基础开关:
spark.dynamicAllocation.enabled:true(启用 DRA)
-
Executor 数量范围:
spark.dynamicAllocation.initialExecutors:初始 Executor 数量。可以设置得较小(如 1 或 2),让应用快速启动。如果设置了spark.executor.instances,它会覆盖此值作为初始值,但 DRA 仍会调整。spark.dynamicAllocation.minExecutors:最小 Executor 数量。即使空闲,也不会低于这个数。避免频繁申请小量 Executor 的开销。重要! 通常 >=1,确保 Driver 有 Executor 可用。spark.dynamicAllocation.maxExecutors:最大 Executor 数量。资源申请的上限,防止单个应用占用整个集群。必须设置! 根据队列配额和总资源设定。
-
Scale Up (申请) 相关:
spark.dynamicAllocation.schedulerBacklogTimeout:首次积压触发申请等待时间。当有待处理任务出现后,等待多久才触发第一次申请 Executor?默认:1s。适用于初始启动或突发负载。spark.dynamicAllocation.sustainedSchedulerBacklogTimeout:持续积压触发后续申请等待时间。在已有积压且已有申请发出后,如果积压仍然存在,等待多久后触发申请下一个 Executor?默认:schedulerBacklogTimeout(通常也是 1s)。控制申请速度。
-
Scale Down (释放) 相关:
spark.dynamicAllocation.executorIdleTimeout:Executor 空闲超时时间。一个 Executor 没有运行任何 Task 且其 Shuffle 数据可安全移除(或已不再需要)后,等待多久会被释放?默认:60s。关键调优点!- 调大 (如 120s, 300s): 减少 Executor 因短暂空闲被频繁启停的开销(启动耗时、JVM 预热),适用于 Task 执行时间波动较大或有批次间隔的应用(如 Structured Streaming)。代价是资源释放稍慢。
- 调小 (如 30s): 更快释放空闲资源,提高集群整体利用率。适用于 Task 密集且执行时间较短的应用。可能增加启停开销。
spark.dynamicAllocation.cachedExecutorIdleTimeout:持有缓存的 Executor 空闲超时时间。如果一个空闲 Executor 的节点上还缓存了 RDD 分区(通过persist()或cache()),则使用此超时(通常远大于executorIdleTimeout,默认:infinity - 永不释放)。避免因释放 Executor 导致缓存失效。如果确定缓存不重要或可重建,可以设置一个有限值。
-
Shuffle 数据跟踪与清理:
spark.dynamicAllocation.shuffleTracking.enabled:是否启用 Shuffle 数据跟踪。默认:true (强烈推荐)。Driver 精确跟踪 Executor 产生的 Shuffle 数据被哪些 Reduce Task 使用,确保数据在被依赖时不会因 Executor 退出而丢失。这是安全释放 Executor 的关键保障。spark.dynamicAllocation.shuffleTracking.timeout:Shuffle 数据保留超时。在最后一个依赖某 Block(Shuffle 数据块)的 Task 完成后,Driver 会等待此超时时间再通知 ESS 删除该 Block。这是一个保守的机制,防止因消息延迟等导致删除过早。默认:executorIdleTimeout* 2 (通常 120s)。一般无需修改,除非遇到非常极端的环境。
-
与集群管理器集成:
- YARN:
- 确保
yarn.nodemanager.aux-services包含spark_shuffle且yarn.nodemanager.aux-services.spark_shuffle.class指向org.apache.spark.network.yarn.YarnShuffleService。这是部署 ESS 的配置。 spark.shuffle.service.enabled:必须设置为true。告诉 Spark 使用 ESS。spark.yarn.shuffle.stopOnFailure:ESS 失败时是否停止 NodeManager(默认 false,生产环境通常保持 false)。
- 确保
- Standalone:
- 在
spark-env.sh中配置SPARK_DAEMON_MEMORY和SPARK_WORKER_OPTS启动 Worker 进程内的 Shuffle Service。 - 同样需要设置
spark.shuffle.service.enabled=true。 - 在
spark-defaults.conf中配置spark.shuffle.service.port(默认 7337)。
- 在
- YARN:
四、 部署、调优步骤与最佳实践
-
部署 External Shuffle Service (ESS):
- YARN: 按照 Spark 官方文档配置
yarn-site.xml并分发。确保所有 NodeManager 重启后加载了新的 Shuffle Service。检查 NodeManager 日志确认 ESS 启动成功。 - Standalone: 在
spark-env.sh中配置好,重启 Worker 进程。 - K8s: Spark 3.0+ 支持,配置更复杂,需部署
spark-shuffleDaemonSet 并使用 K8s 的init-container或sidecar模式。
- YARN: 按照 Spark 官方文档配置
-
配置 Spark 应用启用 DRA:
- 在
spark-defaults.conf(集群级) 或spark-submit --conf(应用级) 设置核心参数:spark.dynamicAllocation.enabled true spark.shuffle.service.enabled true # 必须开启 ESS spark.dynamicAllocation.minExecutors 1 # 推荐 >=1 spark.dynamicAllocation.maxExecutors 100 # 必须设置,根据实际情况调整 spark.dynamicAllocation.initialExecutors 2 # 可调整 spark.dynamicAllocation.executorIdleTimeout 60s # 关键调优参数 # 其他参数根据需要调整
- 在
-
关键调优点:
executorIdleTimeout:- 观察点: 在 Spark UI 的 Executors 页,看 Executor 的 “Active Tasks” 和 “Removed Reasons”。如果看到大量 Executor 因为
idle被移除,但很快又有新任务需要启动 Executor,且移除和新启动之间的间隔很短(小于 JVM 启动+任务调度开销),说明executorIdleTimeout可能设得太小了,导致“抖动”。 - 建议: 结合 Task 的平均执行时间和批次间隔(对于流处理)来设置。如果 Task 执行时间在几秒到几十秒,
60s通常是合理的起点。如果 Task 执行时间非常短(<1s)且密集,可以尝试更小(如30s),但需监控启停开销。如果 Task 执行时间长或存在明显批次空闲期(如 Streaming 的微批次间隔),可以增大(如120s,300s)。
- 观察点: 在 Spark UI 的 Executors 页,看 Executor 的 “Active Tasks” 和 “Removed Reasons”。如果看到大量 Executor 因为
minExecutors/maxExecutors:minExecutors:保证 Driver 至少有一个 Executor 可用(用于运行 Driver 的 Task 或其他必须任务),推荐>=1。对于对延迟敏感的小作业,可以设大点避免冷启动。maxExecutors:严格设置! 根据 YARN 队列配额、集群总资源、应用优先级来设定。防止单个应用耗尽资源。
schedulerBacklogTimeout/sustainedSchedulerBacklogTimeout:- 控制申请速度。如果集群资源充足且希望快速响应积压,保持默认
1s即可。如果担心申请太快冲击集群,可以适当增大(如2s,5s),但这会稍微增加处理积压的延迟。
- 控制申请速度。如果集群资源充足且希望快速响应积压,保持默认
-
最佳实践:
- 必开 ESS: 没有 ESS,DRA 无法安全释放 Executor,几乎等同于无效或极其危险。
- 明确资源边界: 务必设置
minExecutors和maxExecutors。 - 结合监控调优
idleTimeout: 利用 Spark UI 观察 Executor 生命周期和移除原因,避免频繁启停抖动。目标是让 Executor 在真正长时间空闲时才释放。 - 理解缓存影响: 默认持有缓存的 Executor 不会因
idleTimeout释放。如果应用大量缓存且希望释放资源,需权衡:要么接受缓存占用资源,要么调低cachedExecutorIdleTimeout(可能导致缓存失效和重算开销)。 - AQE 协同: Spark 3.0+ 的 Adaptive Query Execution (AQE) 可以动态调整 Shuffle 分区数。DRA 和 AQE 是互补的:DRA 调整 Executor 数量(计算资源),AQE 调整 Task 数量(并行度)。强烈建议同时开启 AQE (
spark.sql.adaptive.enabled=true)。 - 测试验证:
- 提交一个长任务(如循环处理数据),观察 Executor 数量是否随负载变化。
- 观察 Spark UI Executors 页的 “Added” / “Removed” 原因和时间戳。
- 检查集群管理器(YARN RM Web UI)的资源使用情况,看资源是否被及时释放和复用。
- 日志排查: 关注 Driver 日志中关于 DRA 的决策信息(申请/释放 Executor 的原因)。Executor 日志关注与 ESS 的连接和 Shuffle 读写情况。
五、 注意事项与限制
- 非 Shuffle 阶段: DRA 主要根据 Shuffle 边界后的待调度 Task 数量 来决策。对于完全没有 Shuffle 的 Stage(如
map->collect),DRA 可能不会申请超过initialExecutors的 Executor,因为 Driver 看到的是单个 Stage 的 Task,且没有后续 Stage 的积压。对于这种纯 Map 作业,如果希望并行度超过initialExecutors*executor-cores,可能需要手动设置spark.executor.instances或使用coalesce/repartition并配合较大的minExecutors。 - 流式处理 (Structured Streaming): DRA 同样适用。关键调优点是
executorIdleTimeout需要大于微批次间隔 (trigger)。例如,间隔 1min,idleTimeout至少设 2min 或更多,防止 Executor 在批次间隔期间因短暂空闲被移除,导致下个批次需要冷启动 Executor。spark.streaming.dynamicAllocation.enabled(旧 DStream) 和 Structured Streaming 的 DRA 行为略有不同,需注意文档。 - Executor 启动开销: 申请新 Executor 涉及 JVM 启动、网络连接、类加载、注册等过程,需要一定时间(秒级到十秒级)。如果负载增长非常快且需要极低延迟响应,频繁申请可能带来额外延迟。适当增大
minExecutors或调高idleTimeout可以减少申请频率。 - 资源碎片化: 在集群负载极高时,DRA 可能因为申请不到满足资源需求(如特定内存大小)的 Container 而无法扩展,即使集群有总量足够的零散资源。
- 外部依赖: DRA 需要集群管理器(YARN/K8s/Standalone)的支持。Mesos 对 DRA 的支持相对较弱。
总结
Spark 动态资源分配 (DRA) 是提升大型集群资源利用率和作业吞吐量的核心机制。其核心在于按需伸缩 Executor,依赖 External Shuffle Service (ESS) 保障 Shuffle 数据的可用性以实现 Executor 的安全释放。
成功部署调优 DRA 的关键步骤:
- 正确部署并启用 ESS(非可选!)。
- 开启 DRA (
spark.dynamicAllocation.enabled=true)。 - 设置合理的资源范围 (
minExecutors,maxExecutors),maxExecutors必须设。 - 精细调优
executorIdleTimeout,平衡资源释放速度与 Executor 启停开销,利用 Spark UI 监控指导。 - 结合 AQE 使用 获得最佳效果。
- 理解应用特点(批处理/流处理、有无 Shuffle、缓存重要性)进行针对性配置。
- 充分测试验证 决策逻辑和资源变化是否符合预期。
通过合理配置和调优 DRA,可以让 Spark 集群的资源如水一般流动起来,最大化利用效率,支撑更高并发和更复杂的混合负载。
Spark集群动态资源分配部署与调优
989

被折叠的 条评论
为什么被折叠?



