Spark Shuffle、宽依赖和窄依赖是什么?

考查重点:分区依赖与阶段边界。窄依赖可沿父分区直接计算,宽依赖常需 Shuffle 重分数据;解释 groupBy/join 如何形成阶段边界与网络开销

本题目录

参考答案

核心回答

Spark 的宽、窄依赖描述 RDD 之间的分区依赖关系;Shuffle 则是按新的分区规则重新分布数据的过程。

窄依赖下,下游分区可以沿明确的父分区依赖直接计算,不需要通过 Shuffle 重新分发父数据,常见的 map、filter 可以流水线执行。宽依赖通常需要将父分区的数据按 key 或目标分区重新组织,使下游能够汇集分散的数据,对应 Shuffle 依赖。调度器以 Shuffle 依赖划分 Stage,Stage 内可以串联多个窄依赖操作。

Shuffle 往往涉及序列化、网络传输、排序或聚合,以及磁盘 I/O,因此是常见性能开销。但是否产生 Shuffle,要结合已有分区方式和实际执行计划判断,不能只凭算子名称下结论。

窄依赖为什么可以流水线执行

以 map 后接 filter 为例,处理某个下游分区时,直接读取对应父分区,逐条完成映射和过滤,不必等所有其他分区的数据重新汇集。因此,多个操作可以在同一个 Task 中衔接,减少中间结果物化和跨 Stage 的等待。

窄依赖不只限于一对一。例如减少分区时,coalesce 在不启用 Shuffle 的条件下,可以让一个下游分区承接多个父分区。Apache Spark 的 NarrowDependency 源码也从“一个子分区依赖少量父分区”的角度描述它。仅用“子分区依赖多个父分区”来判定宽依赖,会把这种合并误判。

窄依赖也不等于执行时绝无网络访问。数据本地性、缓存位置或读取存储都可能产生远程 I/O;关键是这条依赖不要求 Shuffle 式的数据重分布。

Shuffle 怎样把数据交给下游

假设不同输入分区都包含 key 为 a 的记录,现在要计算每个 key 的总值。若这些记录尚未按目标 key 分区聚集,就需要 Shuffle。

  1. 上游 Task 根据分区规则,为记录确定目标下游分区;适用的聚合可以先在本分区合并部分结果,减少传输量。
  2. Shuffle 写出按目标分区组织的数据块。常见实现会使用内存缓冲,按需要排序、聚合和溢写,产生可供下游读取的中间数据。
  3. 下游 Task 获取属于自己分区的各个上游数据块,读取、合并后完成聚合、连接或排序等操作。

数据可能来自本机,也可能经网络获取;具体存放位置和读写方式取决于 Shuffle 实现。远端 Shuffle 服务还可以把中间数据存储与 Executor 分离,但这不改变其按目标分区重新组织数据的本质。

宽依赖是计算关系,Shuffle 是完成该关系所需的数据交换机制。 RDD 的转换先形成依赖图,Action 触发实际计算,调度器据此安排 Job、Stage 和 Task。一个 Stage 可以包含多个 RDD 转换,一个 Task 通常处理该 Stage 的一个分区;它们不是三个同义概念。

常见算子怎样判断

map、filter、mapPartitions 通常保持各分区独立计算,属于窄依赖。repartition 通过 Shuffle 重新调整数据分布;coalesce 则需要看是否启用 Shuffle,以及是怎样合并分区。

reduceByKey、groupByKey、join 等可能需要 Shuffle,但已有分区器满足目标要求时,可以避免新增的重分布。例如 Pair RDD 已按目标 Partitioner 分区,reduceByKey 可以在相应分区内聚合;RDD join 的两个输入则分别判断,不一定两侧都 Shuffle。分区数量相同并不足以证明分区规则相同,分区器元数据是否被保留也会影响判断。

DataFrame、SQL 中的连接还可能采用广播等物理策略。阅读 explain 和 Spark UI 中的 Exchange、Stage 与 Shuffle 读写指标,比把所有 join 一律归为同一种执行方式更可靠。

为什么慢,优化从哪里开始

先看实际 Shuffle 数据量、读写等待、磁盘溢写、各 Task 耗时与输入分布。大量 Task 都慢,可能是数据量或整体资源问题;少数 Task 明显拖尾,要检查热点 key、分区偏大或资源异常,而不是只增加 Executor。

在业务语义允许时,先过滤无用记录、裁剪列并做局部聚合,减少进入 Shuffle 的数据。只需要按 key 求和等聚合时,优先考虑能局部合并的操作,避免先把全部值收集成大分组。分区数要在单 Task 工作量与调度、文件等开销之间权衡;同一个热点 key 的数据仍可能落到同一分区,单纯增加分区数不一定消除倾斜。

SQL 场景还可以结合适用的广播连接、分区布局与 AQE 检查执行计划。缓存适合被重复使用的数据,并不会自动消除后续仍需重分布的依赖;参数和策略调整后,应重新看计划与指标,不能只以配置已打开作为优化结果。

常见追问

只有 100 个输入分区,下游计算很重,想扩到 1000 个怎么办?

如果需要真正把数据分散给更多下游 Task,可以在合适位置使用 repartition,它会付出 Shuffle 成本。仅修改默认并行度,不会自动把已经形成的 RDD 分区重新切开。是否值得扩分区,还要看数据能否均衡拆分,以及计算量是否足以抵消交换和调度开销。

Shuffle 后的数据天然有序吗?

按 key 聚集或按目标分区重分布,不等于保证记录顺序。需要全局或分区内顺序时,应使用相应排序操作,并明确排序范围。

参考资料

  • 国内:华为云 MRS:Spark 基本原理:借鉴 RDD 依赖、流水线与 Stage 的说明,解释正文中分区依赖对执行的影响;宽依赖定义按分区器和源码限定,未采用“必依赖所有父分区”的概括。
  • 国内:腾讯云 DLC:查询性能优化指南:Exchange、聚合与 Stage 指标的分析说明怎样从实际执行计划定位 Shuffle 开销;其中服务参数与调优建议需要结合所用引擎版本和任务验证。
  • 国内:阿里云 EMR:Celeborn 介绍:参考远端中间数据存储、读写和 Stage 重算的机制,说明正文中 Shuffle 数据可与 Executor 解耦;该架构仍作为具体实现例子。
  • 国外:Apache Spark:RDD Programming Guide:依据 Shuffle 重分布、聚合、排序与 I/O 的说明,组织正文的计算步骤及算子行为。
  • 国外:Apache Spark:Tuning:序列化、并行度和 Reduce Task 工作集部分,解释 Shuffle 为什么会消耗网络、内存和磁盘,以及分区调整能够解决哪些问题。

相关面经

3 篇

OPPO

数据开发工程师
秋招一面
查看原面经 →
Spark Shuffle原理,宽依赖、窄依赖

字节跳动

大数据开发工程师
校招秋招一面
查看原面经 →
你了解Spark的依赖吗?
Spark的依赖中,哪些操作对应窄依赖,哪些对应宽依赖?

阶跃星辰

大模型基础设施工程师(数据管道)
实习秋招一面、二面
查看原面经 →
Spark宽窄依赖、spark介绍一下

相关题目

4 题