Spark 窄依赖和宽依赖

目录:

一. 简介
二. 区别
三.stage 划分

一. 简介

Spark 中 RDD 的高效与 DAG 图有着莫大的关系,在 DAG 调度中需要对计算过程划分 stage,而划分依据就是 RDD 之间的依赖关系。针对不同的转换函数,RDD 之间的依赖关系分类窄依赖(narrow dependency)和宽依赖(wide dependency, 也称 shuffle dependency)

二. 区别

窄依赖是指父 RDD 的每个分区只被子 RDD 的一个分区所使用,子 RDD 分区通常对应常数个父 RDD 分区 (O(1),与数据规模无关 )

宽依赖是指父 RDD 的每个分区都可能被多个子 RDD 分区所使用,子 RDD 分区通常对应所有的父 RDD 分区 (O(n),与数据规模有关 )
2020/08/image-f2a0a0f4.png
相比于宽依赖,窄依赖对优化很有利 ,主要基于以下两点:

1. 宽依赖往往对应着 shuffle 操作 (多对一, 汇总, 多节点),需要在运行过程中将同一个父 RDD 的分区传入到不同的子 RDD 分区中,中间可能涉及多个节点之间的数据传输;而窄依赖的每个父 RDD 的分区只会传入到一个子 RDD 分区中,通常可以在一个节点内完成转换。

2. 当 RDD 分区丢失时(某个节点故障),spark 会对数据进行重算。

a. 对于窄依赖,由于父 RDD 的一个分区只对应一个子 RDD 分区,这样只需要重算和子 RDD 分区对应的父 RDD 分区即可,所以这个重算对数据的利用率是 100% 的;
b. 对于宽依赖,重算的父 RDD 分区对应多个子 RDD 分区,这样实际上父 RDD 中只有一部分的数据是被用于恢复这个丢失的子 RDD 分区的,另一部分对应子 RDD 的其它未丢失分区,这就造成了多余的计算;更一般的,宽依赖中子 RDD 分区通常来自多个父 RDD 分区,极端情况下,所有的父 RDD 分区都要进行重新计算。
c. 如下图所示,b1 分区丢失,则需要重新计算 a1,a2 和 a3,这就产生了冗余计算 (a1,a2,a3 中对应 b2 的数据)。

2020/08/image-0eec184a.png

区分这两种依赖很有用。
首先,窄依赖允许在一个集群节点上以流水线的方式(pipeline)计算所有父分区。例如,逐个元素地执行 map、然后 filter 操作;而宽依赖则需要首先计算好所有父分区数据,然后在节点之间进行 Shuffle,这与 MapReduce 类似。

第二,窄依赖能够更有效地进行失效节点的恢复,即只需重新计算丢失 RDD 分区的父分区,而且不同节点之间可以并行计算;而对于一个宽依赖关系的 Lineage 图,单个节点失效可能导致这个 RDD 的所有祖先丢失部分分区,因而需要整体重新计算。

窄依赖的函数有:map, filter, union, join(父 RDD 是 hash-partitioned), mapPartitions, mapValues
宽依赖的函数有:groupByKey, join(父 RDD 不是 hash-partitioned), partitionBy

三.stage 划分

2020/08/image-4f89101a.png

Stage 划分思路
因此 spark 划分 stage 的整体思路是:从后往前推,遇到宽依赖就断开,划分为一个 stage;遇到窄依赖就将这个 RDD 加入该 stage 中。因此在图 2 中 RDD C,RDD D,RDD E,RDDF 被构建在一个 stage 中,RDD A 被构建在一个单独的 Stage 中, 而 RDD B 和 RDD G 又被构建在同一个 stage 中。
  在 spark 中,Task 的类型分为 2 种:ShuffleMapTask 和 ResultTask;简单来说,DAG 的最后一个阶段会为每个结果的 partition 生成一个 ResultTask,即每个 Stage 里面的 Task 的数量是由该 Stage 中最后一个 RDD 的 Partition 的数量所决定的!而其余所有阶段都会生成 ShuffleMapTask;之所以称之为 ShuffleMapTask 是因为它需要将自己的计算结果通过 shuffle 到下一个 stage 中;也就是说图 2 中的 stage1 和 stage2 相当于 mapreduce 中的 Mapper, 而 ResultTask 所代表的 stage3 就相当于 mapreduce 中的 reducer。

总结
map,filtre 为窄依赖,
groupbykey 为款依赖
遇到一个宽依赖就分一个 stage