Spark中的宽依赖(Wide Dependency)和窄依赖(Narrow Dependency)
在 Apache Spark 中,宽依赖(Wide Dependency)和窄依赖(Narrow Dependency)是描述父 RDD(Parent RDD)与子 RDD(Child RDD)之间分区(Partition)对应关系的核心概念。
理解这两者的区别对于优化 Spark 任务性能、理解 Stage 的划分以及容错机制至关重要。
1. 窄依赖 (Narrow Dependency)
定义:
指父 RDD 的每一个分区最多被子 RDD 的一个分区所使用。即“一对一”或“多对一”(但数据不跨节点流动)。
- 形象理解: 独生子女。父 RDD 的数据像流水线一样直接流向子 RDD,不需要跨节点传输。
- 常见算子:
mapfilterflatMapunioncoalesce(通常情况)join(如果两个父 RDD 已经基于相同的 Partitioners 进行了协同划分/Co-partitioned)
- 特点:
- Pipelining(流水线优化): Spark 可以将多个窄依赖操作合并到一个 Task 中执行(例如:先 map 再 filter),数据在内存中直接转换,无需落盘或网络传输。
- 容错成本低: 如果某个子 RDD 分区丢失,只需要重新计算对应的父 RDD 分区即可,不需要计算所有父分区。
图示:
plaintext
[父分区1] ---> [子分区1]
[父分区2] ---> [子分区2]
2. 宽依赖 (Wide Dependency) / Shuffle Dependency
定义:
指父 RDD 的每一个分区都可能被子 RDD 的多个分区所使用。即“一对多”或“多对多”。
- 形象理解: 超生/交际花。数据需要在集群中重新洗牌(Shuffle),不同节点的数据需要交换。
- 常见算子:
groupByKeyreduceByKeysortByKeydistinctrepartitionjoin(通常情况,即未协同划分时)
- 特点:
- Shuffle(洗牌): 必须发生 Shuffle。数据需要写入磁盘,并通过网络传输到其他节点。这是 Spark 中最消耗性能的操作。
- Stage 划分界限: 宽依赖是划分 Stage(阶段)的边界。
- 容错成本高: 如果子 RDD 分区丢失,通常需要重新计算父 RDD 的所有分区,因为数据是混在一起的。
图示:
plaintext
[父分区1] --+--> [子分区1]
\--> [子分区2]
[父分区2] --+--> [子分区1]
\--> [子分区2]
3. 核心区别对比表
| 特性 | 窄依赖 (Narrow) | 宽依赖 (Wide) |
|---|---|---|
| 分区对应关系 | 父分区对应 0 或 1 个子分区 | 父分区对应多个子分区 |
| 是否发生 Shuffle | 否 | 是 (必须 Shuffle) |
| 数据传输 | 内存中直接流动 (Pipeline) | 跨节点网络传输 + 磁盘读写 |
| Stage 划分 | 在同一个 Stage 内部 | 划分新 Stage 的边界 |
| 常见算子 | map, filter, union | groupByKey, reduceByKey, repartition |
| 容错恢复 | 仅重算丢失的那个分区 | 可能需要重算父 RDD 所有分区 |
4. 为什么这个区分很重要?
A. Stage 的划分 (DAG Scheduler)
Spark 的 DAGScheduler 会根据依赖关系将 Job 划分为不同的 Stage。
- 划分规则: 从后往前推,遇到宽依赖就切一刀,断开的地方就是一个 Stage 的边界。
- 结果: 窄依赖会被放入同一个 Stage 中进行流水线执行(TaskSet),而宽依赖则必须等待上一个 Stage 的所有 Shuffle Map Task 完成后,才能开始下一个 Stage。
B. 性能优化
- 窄依赖快: 因为都在内存中进行,且支持流水线。
- 宽依赖慢: 涉及磁盘 I/O 和网络 I/O。
- 优化思路: 尽量减少宽依赖的使用,或者在宽依赖之前先进行数据过滤(filter),减少 Shuffle 的数据量。例如,
reduceByKey比groupByKey性能好,因为reduceByKey会在 Shuffle 前进行预聚合(Map 端预聚合),这其实利用了窄依赖的思想来减少宽依赖的数据传输。
C. 容错 (Fault Tolerance)
Spark 的 Lineage(血统)机制依赖于此。
- 在窄依赖中,如果节点故障导致分区丢失,Spark 只需要找到父 RDD 对应的那个分区重算即可,非常快。
- 在宽依赖中,因为数据是混洗的,一个子分区的数据来自所有父分区。如果 Shuffle 的中间结果丢失,可能需要重新运行整个上游 Stage。
总结
窄依赖是 Spark 速度的保证(内存计算、流水线),宽依赖是 Spark 复杂逻辑的实现(聚合、排序),但也是性能瓶颈的所在。
右滑查看面试常问