订阅更新

Spark 宽依赖和窄依赖有什么区别?

面试问 036 约 4 分钟读完 3 次阅读

本文目录
  1. 面试怎么回答
  2. 第一步:窄依赖可以在同一个 Stage 流水执行
  3. 第二步:宽依赖需要跨分区重新分组
  4. 第三步:宽依赖的成本不只是网络传输
  5. 第四步:优化目标不是消灭所有宽依赖
  6. 一个典型场景

面试怎么回答

窄依赖中,子分区只依赖少量确定的父分区,多个算子可以在同一个 Stage 流水执行,失败时通常只重算相关分区。宽依赖中,子分区需要多个父分区的数据重新分组,会产生 Shuffle,并形成 Stage 边界。它会增加网络、磁盘、序列化、内存和倾斜成本。优化不是消灭宽依赖,而是通过提前过滤、Map 端预聚合、Broadcast 和合理分区减少不必要的数据搬运。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

很多人会回答:

“窄依赖不发生 Shuffle,宽依赖会发生 Shuffle。”

这句话没错,但面试官通常会继续问:为什么宽依赖会切 Stage?为什么它更慢?对容错有什么影响?

真正需要理解的是:

依赖类型描述的是子分区需要哪些父分区的数据,它决定能否流水线执行、是否需要 Shuffle,以及任务失败后的重算边界。


第一步:窄依赖可以在同一个 Stage 流水执行

窄依赖中,一个父分区的数据只会被少量确定的子分区使用,一个子分区也不需要等待所有父分区的数据重新分组。

常见操作包括:

  • map
  • filter
  • flatMap
  • mapPartitions
  • 不触发 Shuffle 的 coalesce

例如:

RDD A 分区 0 → RDD B 分区 0 → RDD C 分区 0

Spark 可以把这些 Transformation 合并在同一个 Stage 的 Task 中流水执行,不需要把中间结果跨节点重新分发。

某个分区丢失时,也通常只需要沿着 Lineage 重算与该分区相关的父分区。


第二步:宽依赖需要跨分区重新分组

宽依赖中,一个子分区的数据可能来自多个父分区,同一个父分区的数据也可能被发送到多个子分区。

常见操作包括:

  • groupByKey
  • reduceByKey
  • distinct
  • repartition
  • 普通大表 join

例如按 Key 聚合时,相同 Key 可能分散在所有上游分区。Spark 必须先完成上游 Map Task,把记录按目标 Partition 写入 Shuffle,再由下游 Reduce Task 拉取对应数据。

因此 Shuffle 会形成 Stage 边界:下游 Stage 需要等待上游 Shuffle Map 输出可用,不能像窄依赖一样直接串在同一条流水线上。

Spark 宽窄依赖与 Stage 边界


第三步:宽依赖的成本不只是网络传输

Shuffle 通常会引入:

  • 跨 Executor 网络传输
  • Shuffle 文件写盘和读取
  • 序列化与反序列化
  • 内存排序、聚合和 Spill
  • 更多 GC 与磁盘 I/O
  • 数据倾斜导致的长尾 Task

所以看到宽依赖时,不能只说“有网络开销”。真正的性能问题还取决于数据量、Key 分布、分区数、聚合方式和磁盘能力。

宽依赖也影响故障恢复。如果下游 Task 失败,通常可以重新读取已有 Shuffle 输出;如果保存 Shuffle 文件的 Executor 丢失,对应上游 Shuffle Map Task 可能需要重算。


第四步:优化目标不是消灭所有宽依赖

按 Key 聚合、去重和大表 Join 本来就需要重新分区,宽依赖并不是错误。

优化重点是减少不必要的数据搬运:

  • 能在 Map 端预聚合时,优先 reduceByKey 或 aggregateByKey
  • 小表 Join 评估 Broadcast,避免大规模 Shuffle
  • 过滤和字段裁剪尽量放在 Shuffle 前
  • 避免连续无意义的 repartition
  • 根据数据量设置合理 Shuffle Partition
  • 检查热点 Key,必要时拆分或两阶段聚合

还要结合 Spark UI 或执行计划确认实际 Stage 和 Shuffle 指标,而不是只凭代码方法名判断。


一个典型场景

比如统计每个用户的订单金额,代码先使用:

groupByKey().mapValues(sum)

它会把同一个用户的所有明细跨网络拉到同一个分区,再一次性聚合。大用户可能占用大量内存并触发 Spill。

改成 reduceByKey 后,可以先在 Map 端对每个分区内的同 Key 数据局部聚合,再传输中间结果。

两者都存在宽依赖和 Shuffle,但后者通常减少了网络传输量和 Reduce 端内存压力。

理解宽窄依赖,不是为了背算子名单,而是为了看懂 Stage 为什么切开、数据为什么要移动。


刷题进度保存在当前浏览器