Spark 窄依赖和宽依赖的区别?
考察意图
考察候选人对 Spark 执行图的本质理解:依赖类型决定要不要 Shuffle、Stage 怎么切、失败了怎么恢复。答成「map 是窄、groupBy 是宽」不够,要能讲清分区对应关系与代价。
回答框架
- 先给定义:看父 RDD 的一个分区,会被几个子分区用到。
- 对照典型算子:map/filter 是窄;groupByKey/reduceByKey/join 是宽。
- 落到执行:宽依赖触发 Shuffle,是 Stage 边界。
- 补容错差异:窄依赖可从父分区局部重算,宽依赖常要重做整个 Shuffle。
参考答案(口述版)
一句话:窄依赖是「父分区一对一(或固定少量)喂给子分区」;宽依赖是「父分区的记录要按键打散到多个子分区」。宽依赖通常意味着 Shuffle。
窄依赖(Narrow Dependency):
- 每个父分区最多被一个子分区使用,分区关系编译期就确定。
- 典型:map、filter、flatMap、union、mapPartitions;对已按同一键分区好的 join/cogroup 也可能是窄的(co-partition)。
- 好处:pipeline 在同一个 Stage 里流水执行,不必跨节点换数据;某个分区失败,只重算对应的父分区即可。
宽依赖(Wide / Shuffle Dependency):
- 一个父分区的数据可能进入多个子分区,要等上游写完 shuffle 文件,下游按键拉取。
- 典型:groupByKey、reduceByKey、join(未同分区)、distinct、repartition、sortByKey。
- DAGScheduler 在宽依赖处切 Stage:窄依赖串成一个 Stage 流水线,宽依赖是 Stage 之间的边界。
面试加分可以落到代价:Shuffle 有序列化、网络、落盘、内存缓冲,是作业变慢和 OOM 的高发区。所以能 map-side combine 的(reduceByKey)优于先全量 groupByKey;小表能广播就别走大 join。
对照四句话带走:
1. 分区关系:窄是一对一(确定),宽是按键一对多
2. Shuffle:窄没有,宽有
3. Stage:窄走同一 Stage 的 pipeline,宽是 Stage 分界
4. 失败恢复:窄可局部重算父分区,宽常要重做 Shuffle 段
关键词
面试官可能的追问
追问 1:为什么 reduceByKey 通常比 groupByKey 更合适?
两者都是宽依赖,都要 Shuffle。但 reduceByKey 能在 map 端先局部聚合,再把部分聚合结果 shuffle 出去,网络量和 reduce 端压力都更小。groupByKey 是把同一 key 的全部记录搬到一起再算,倾斜时更容易 OOM。能预聚合就不要先全量聚集。
追问 2:Stage 是怎么切出来的?和窄/宽依赖有什么关系?
从最终 RDD 往回看:一路窄依赖就还在同一个 Stage 里流水;碰到宽依赖就断开,上游成为父 Stage。所以「有几个宽依赖」大致决定「有几个 Shuffle Stage」。调优时先看 Spark UI 的 Stage 边界,比猜算子更准。
常见踩坑
- 把「有没有 Shuffle」和算子名字死记,忽略已经 co-partition 的 join 可以是窄依赖
- 把窄依赖理解成「一定只有一个父 RDD」——union 是窄依赖但有多个父
- 只背定义,说不出 Stage 切分和失败恢复的差别
记忆锚点
窄依赖流水线、宽依赖 Shuffle;Stage 就切在宽依赖上。