Spark 核心之 Stage 切分划分与计算模式详解

摘要:DAGScheduler 拿到 finalRDD 后,如何一步步回溯出完整的 Stage DAG?为什么 Stage 内部可以 Pipeline 执行而 Stage 之间必须串行?数据本地性如何影响 Task 分发?本文从四步切分算法、Pipeline 函数组合原理、递归提交顺序、数据本地性调度、Stage 间串行 vs Stage 内并行五个维度,配合 1 张原创深色架构图 + 完整源码分析,带你彻底理解 Spark Stage 的切分逻辑与计算模式。

关键词:Spark Stage, DAGScheduler, Pipeline, 递归提交, 数据本地性, ShuffleMapStage, ResultStage, TaskSet


一、开篇:一个 collect() 如何变成两个 Stage?

scala 复制代码
sc.textFile("hdfs:///data")
  .flatMap(_.split(" "))
  .map((_, 1))
  .reduceByKey(_ + _)
  .filter(_._2 > 10)
  .collect()

这段代码会产生 2 个 StagetextFile→flatMap→map 是 Stage 0(3 个 NarrowDep),reduceByKey→filter→collect 是 Stage 1(1 个 WideDep 切分点)。

Stage 不是"写出来的",而是 DAGScheduler "算出来的"。


二、Stage 切分算法全景图


三、切分算法四步走

Step 1: DAGScheduler 从 finalRDD 出发

scala 复制代码
// 源码:DAGScheduler.scala
private def createResultStage(
    finalRDD: RDD[_], func: ..., jobId: Int, ...): ResultStage = {
  val parents = getOrCreateParentStages(finalRDD, jobId)  // 回溯
  val id = nextStageId.getAndIncrement()
  new ResultStage(id, finalRDD, func, partitions, parents, jobId, ...)
}

Step 2-3: 递归回溯 RDD.dependencies → 遇 Shuffle 则切分

scala 复制代码
private def getOrCreateParentStages(rdd: RDD[_], firstJobId: Int): List[Stage] = {
  rdd.dependencies.flatMap {
    case shufDep: ShuffleDependency[_, _, _] =>
      getOrCreateShuffleMapStage(shufDep, firstJobId) :: Nil   // ← 切分点
    case _: NarrowDependency[_] =>
      Nil  // ← 同 Stage,继续往前找
  }.toList
}

Step 4: submitStage() 递归提交------先父后子

scala 复制代码
private def submitStage(stage: Stage): Unit = {
  val missing = getMissingParentStages(stage).sortBy(_.id)
  if (missing.isEmpty) {
    submitMissingTasks(stage, jobId.get)  // 无父 → 直接执行
  } else {
    for (parent <- missing) submitStage(parent)  // 有父 → 先执行父
  }
}

提交顺序:submitStage(Stage1) → 发现父 Stage0 未完成 → submitStage(Stage0) → Stage0 全部 Task 完成 → 回到 submitStage(Stage1) → 执行。


四、Pipeline 计算模式

Stage 内所有 NarrowDep 的算子不是依次执行 ,而是被组合成一个迭代器流水线

scala 复制代码
// RDD.iterator() 的嵌套调用链
// textFile → flatMap → map 被编译为:
rdd1.iterator(partition, context)  // textFile
  .flatMap(f1)                     // flatMap
  .map(f2)                         // map
// 等价于 f2(f1(read(partition))) → 逐条流水线,不落盘

Pipeline 优化效果:数据从 HDFS 读出后直接流经 flatMap → map,中途不落盘、不跨网络,极大减少 I/O。


五、数据本地性调度

TaskScheduler 分发 Task 时遵循延迟调度策略:

scss 复制代码
PROCESS_LOCAL (最优) → NODE_LOCAL → RACK_LOCAL → ANY
   0ms 立即调度     → 等待 3s    → 等待 6s   → 强制调度
bash 复制代码
# 调整本地性等待时间
--conf spark.locality.wait.process=3s   # PROCESS_LOCAL 等待
--conf spark.locality.wait.node=3s      # NODE_LOCAL 等待
--conf spark.locality.wait.rack=3s      # RACK_LOCAL 等待

六、Stage 间串行 vs Stage 内并行

维度 Stage 间 Stage 内
执行模式 串行(父子依赖) 并行(Partition 级)
桥接 MapOutputTracker ---
最大并行度 --- Partition 数 = Task 数

七、总结

要点 总结
切分算法 finalRDD 回溯 → 遇 ShuffleDep 切分 → 递归构建 Stage DAG
Pipeline Narrow 算子函数组合成迭代器链,逐条流水线执行
提交顺序 submitStage 递归 → 先父后子 → MapOutputTracker 桥接
并行度 Stage 间串行,Stage 内并行(Partition 数 = Task 数)

金句:Stage 切分不是"人工标注"的------它是 DAGScheduler 从 RDD 血缘中自动推断出的最优执行计划。Narrow 是一气呵成的流水线,Wide 是必须交接的中转站。


作者:starzy | AI Data Engineer / 大数据技术实践者

博客:blog.starzy.cn | GitHub:starzy1990.github.io

专注 AI Agent · LangGraph · RAG · 大数据架构 · 数据工程实践

相关推荐
用户36105886261217 小时前
Spark 核心之 RDD 窄依赖与宽依赖详解
spark
用户3610588626121 天前
Spark 核心之 Stage 和 Task 原理剖析
spark
java1234_小锋1 天前
【免费】基于Spark实时医疗健康数据监测与疾病预测系统(Java版本+可视化大屏+Kafka+SpringBoot+Vue3) 锋哥原创出品,必属精品
大数据·spark·kafka·实时医疗健康数据监测·实时疾病预测系统
腾讯云大数据1 天前
腾讯云智能数据湖计算AI DLC发布会回顾:Spark+Ray一体化,面向Agent重构数据底座
人工智能·spark·腾讯云
用户3610588626122 天前
Spark 核心之 Application 和 Job 原理剖析
spark
阿里云大数据AI技术3 天前
EMR Serverless Spark AI Function 的双维降本实践
人工智能·sql·spark
java1234_小锋3 天前
【免费】基于Spark实时交通流量分析与拥堵预测系统(Java版本+可视化大屏+Kafka+SpringBoot+Vue3) 锋哥原创出品,必属精品
java·大数据·spark·kafka·实时交通流量分析与拥堵预测
用户3610588626123 天前
Spark 核心之 ClusterManager 原理剖析
spark