Spark 核心之 RDD 窄依赖与宽依赖详解

摘要 :为什么 map().filter() 可以在一个 Stage 内完成,而 reduceByKey() 必须切分新的 Stage?为什么 Shuffle 是 Spark 性能的头号杀手?答案就在 RDD 的依赖类型之中------NarrowDependency 与 ShuffleDependency。本文从依赖类型体系、Stage 边界判定源码、Pipeline 优化原理、容错策略差异、性能影响与调优五个维度,配合 1 张原创深色架构图 + 完整源码分析,带你彻底理解 Spark 最核心的概念之一。

关键词:Spark RDD, NarrowDependency, ShuffleDependency, Stage, Pipeline, 血缘, 容错, Shuffle


一、开篇:为什么依赖类型决定一切?

先看两段等价代码:

scala 复制代码
// 方案 A: 全 Narrow --- 1 个 Stage, 无 Shuffle
rdd.map(_ + 1).filter(_ > 10).collect()

// 方案 B: 包含 Wide --- 2 个 Stage, 有 Shuffle
rdd.map(_ + 1).reduceByKey(_ + _).collect()

方案 A 只有 1 个 Stage ,因为 mapfilter 都是窄依赖------子 Partition 只依赖父 Partition 的 1 对 1 映射,Spark 可以将它们 Pipeline 在一起 执行。

方案 B 有 2 个 Stage ,因为 reduceByKey 是宽依赖------子 Partition 需要从多个父 Partition 拉取数据,必须通过 Shuffle 重新分区。


二、NarrowDependency vs ShuffleDependency 全景图

三、NarrowDependency(窄依赖)

3.1 三种子类型

scala 复制代码
// 源码:Dependency.scala
abstract class NarrowDependency[T](_rdd: RDD[T]) extends Dependency[T] {
  // 子 Partition 依赖父 RDD 的哪些 Partition
  def getParents(partitionId: Int): Seq[Int]
}

// 1:1 映射
class OneToOneDependency[T](rdd: RDD[T]) extends NarrowDependency[T](rdd) {
  override def getParents(partitionId: Int) = List(partitionId)
}

// Range 依赖 (union)
class RangeDependency[T](rdd: RDD[T], inStart: Int, outStart: Int, length: Int)
  extends NarrowDependency[T](rdd) {
  override def getParents(partitionId: Int) =
    if (partitionId >= outStart && partitionId < outStart + length)
      List(partitionId - outStart + inStart) else Nil
}

3.2 哪些算子产生 NarrowDependency?

算子 依赖类型 说明
map OneToOneDependency 1→1 映射
filter OneToOneDependency 1→1 映射(过滤后仍独立)
flatMap OneToOneDependency 1→1 映射
mapPartitions OneToOneDependency 1→1 映射
union RangeDependency 多 RDD 拼接
coalesce NarrowDependency 减少 Partition(不涉及 Shuffle 时)
sample OneToOneDependency 采样

四、ShuffleDependency(宽依赖)

4.1 源码结构

scala 复制代码
class ShuffleDependency[K, V, C](
    @transient private val _rdd: RDD[_ <: Product2[K, V]],
    val partitioner: Partitioner,        // HashPartitioner / RangePartitioner
    val serializer: Serializer = SparkEnv.get.serializer,
    val keyOrdering: Option[Ordering[K]] = None,
    val aggregator: Option[Aggregator[K, V, C]] = None, // mapSideCombine
    val mapSideCombine: Boolean = false)
  extends Dependency[Product2[K, V]] {
  val shuffleId: Int = _rdd.context.newShuffleId()
  val shuffleHandle: ShuffleHandle = ... // SortShuffleManager 生成
}

4.2 ShuffleDependency 的五个关键属性

属性 说明 影响
partitioner HashPartitioner / RangePartitioner 决定数据如何分布到下游 Partition
serializer Kryo / Java 序列化 Shuffle 中间数据
keyOrdering 排序器 sortByKey 必须指定
aggregator 聚合器 reduceByKey 的 mapSideCombine
mapSideCombine 是否 Map 端预聚合 reduceByKey vs groupByKey 的关键区别

4.3 reduceByKey vs groupByKey

scala 复制代码
// ❌ groupByKey: mapSideCombine=false --- 全量数据传输
rdd.groupByKey()  // Shuffle 传输所有 KV 对

// ✅ reduceByKey: mapSideCombine=true --- Map 端预聚合
rdd.reduceByKey(_ + _)  // Shuffle 前先在 Map 端合并

性能差异reduceByKeygroupByKey 减少 90%+ 的 Shuffle 数据量(取决于 Key 的重复率)。


五、依赖类型决定 Stage 边界

scala 复制代码
// 源码:DAGScheduler.scala
private def getOrCreateParentStages(rdd: RDD[_], firstJobId: Int): List[Stage] = {
  rdd.dependencies.flatMap {
    case shufDep: ShuffleDependency[_, _, _] =>
      // 🔥 遇到 Shuffle → 创建 ShuffleMapStage → Stage 边界
      getOrCreateShuffleMapStage(shufDep, firstJobId) :: Nil
    case _: NarrowDependency[_] =>
      Nil  // Narrow → 同 Stage, 不切分
  }.toList
}

规则 :遇到 ShuffleDependency 即切分 Stage,NarrowDependency 全部在同一 Stage 内 Pipeline 执行。


六、容错策略差异

维度 Narrow Wide
容错范围 只重算丢失 Partition 重算整个 Stage(因为上游数据需要重新 Shuffle)
血缘链 短(Pipeline 内) 长(跨 Stage)
恢复成本 (Shuffle 重复 I/O)
Checkpoint 通常不需要 强烈建议在 Shuffle 后 Checkpoint

七、性能优化建议

bash 复制代码
# 1. 用 reduceByKey 代替 groupByKey(开启 mapSideCombine)
# 2. 用 Kryo 序列化减少 Shuffle 数据量
--conf spark.serializer=org.apache.spark.serializer.KryoSerializer

# 3. 调大 Shuffle 分区数避免数据倾斜
--conf spark.sql.shuffle.partitions=400

# 4. 启用 Shuffle 文件合并
--conf spark.shuffle.consolidateFiles=true

八、总结

要点 总结
NarrowDependency 1→1 / Range / Prune, 同 Stage Pipeline, 容错轻
ShuffleDependency 多→多, Stage 边界, Shuffle I/O 重, 容错贵
Stage 切分 遇 ShuffleDependency 即切分
优化核心 减少 Shuffle = 减少 Stage = 提升性能

金句:Narrow 是团队内部的接力赛(一棒接一棒,全在一个 Stage),Wide 是部门之间的邮件沟通(必须写好 Shuffle 文件,下一个 Stage 再来读)。


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

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

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

相关推荐
Q26433650235 小时前
【有源码】基于机器学习的商场商铺运营分群与可视化分析研究 基于Spark的商场商铺经营效率分析与可视化
大数据·hadoop·机器学习·数据挖掘·数据分析·spark·毕业设计
木圭的AI时代指南17 小时前
Spark-X2.5 长输入 Agent 实测:全量读取陷阱与解释器寻找死循环复盘,附可复现任务
大数据·分布式·spark
Q26433650231 天前
【有源码】基于机器学习的电信网络诈骗话术语义识别与可视化分析系统 XGBoost算法+Hadoop+Spark
大数据·hadoop·算法·机器学习·spark·毕业设计·课程设计
Q26433650231 天前
【有源码】基于 Hadoop 的强化学习智能体轨迹数据挖掘与可视化分析系统大数据毕设项目
大数据·hadoop·机器学习·数据挖掘·spark·课程设计·数据可视化
Q26433650231 天前
【有源码】基于大数据的药品多维度属性分析可视化系统 基于Spark的药品属性聚类与关联规则可视化分析系统
hadoop·机器学习·数据挖掘·spark·毕业设计·课程设计·大数据项目
Q26433650231 天前
【有源码】基于大数据的零售交易者行为特征与生存状况数据分析及可视化 Hadoop+Spark大数据项目
大数据·hadoop·信息可视化·数据挖掘·数据分析·spark·毕业设计
Q26433650231 天前
【有源码】基于 Hadoop 生态的化妆品销售数据存储分析与可视化 面向化妆品行业的用户画像构建与销售机会识别研究
大数据·hadoop·python·机器学习·spark·毕业设计·课程设计
计算机源码社2 天前
基于Hadoop+Spark的黄金市场历史数据特征分析与可视化大屏 基于K-Means聚类算法的黄金历史价格阶段划分研究
大数据·hadoop·python·数据挖掘·数据分析·spark·毕业设计
AIsocial3 天前
Spark Ads怎么用?自然流量测试内容,再用付费流量放大
spark·tiktok
IT毕设梦工厂4 天前
计算机毕业设计选题推荐:基于大数据的印度上市公司财务指标数据可视化分析|毕业设计选题|计算机毕设|选题推荐|毕设指导|项目定制|源码|高质量项目
大数据·hadoop·python·信息可视化·spark·课程设计·大数据毕设项目