一般观点:
而实际上:
算子:
窄依赖算子:map、filter、union、join(hash-partitioned)、mapPartitions;
宽依赖算子:join(非hash-partitioned)、groupByKey、partitionBy;
代码位于:spark/core/scala/Dependency.scala
源码中反查RangeDependency的引用情况,只有UnionRDD引用到了
class UnionRDD[T: ClassTag](
sc: SparkContext,
var rdds: Seq[RDD[T]])
extends RDD[T](sc, Nil) { // Nil since we implement getDependencies
override def getDependencies: Seq[Dependency[_]] = {
val deps = new ArrayBuffer[Dependency[_]]
var pos = 0
for (rdd <- rdds) {
deps += new RangeDependency(rdd, 0, pos, rdd.partitions.length)
pos += rdd.partitions.length
}
deps
}
}
源码中反查OneToOneDependency的引用情况:
在官方文档中 Spark 2.4.7 ScalaDoc,窄依赖的描述为:
Base class for dependencies where each partition of the child RDD depends on a small number of partitions of the parent RDD. Narrow dependencies allow for pipelined execution.
即 child RDD 中的每个分区都依赖 parent RDD 中的一小部分分区。那么如何理解这句话呢,我们首先来看下面这张图。
本图囊括了有关窄依赖的各种依赖情况,我们一一来看。
一对一依赖。从图中我们可以看出,child RDD 中的每个分区都只依赖 parent RDD 中的一个分区,并且 child RDD 的分区数和 parent RDD 的分区数相同。这种我们称之为 OneToOneDependency。属于这种依赖关系的转换算子有 map()、flatMap()、filter() 等。通过阅读 Spark 源码,我们可以发现,这些算子生成的 RDD 的依赖关系使用的就是 OneToOneDependency 这个类。
class OneToOneDependency[T](rdd: RDD[T]) extends NarrowDependency[T](rdd) {
override def getParents(partitionId: Int): List[Int] = List(partitionId)
}
范围依赖。child RDD 和 parent RDD 的分区经过划分,每个范围内的父子 RDD 的分区都为一一对应的关系。属于这种依赖关系的转换算子有 union() 等。通过阅读源码,我们可以看到,在 UnionRDD 的 getDependencies() 方法中,创建了一个 RangeDependency 类。
class RangeDependency[T](rdd: RDD[T], inStart: Int, outStart: Int, length: Int)
extends NarrowDependency[T](rdd) {
override def getParents(partitionId: Int): List[Int] = {
if (partitionId >= outStart && partitionId < outStart + length) {
List(partitionId - outStart + inStart)
} else {
Nil
}
}
}
class UnionRDD[T: ClassTag](
sc: SparkContext,
var rdds: Seq[RDD[T]])
extends RDD[T](sc, Nil) {
override def getDependencies: Seq[Dependency[_]] = {
val deps = new ArrayBuffer[Dependency[_]]
var pos = 0
for (rdd <- rdds) {
deps += new RangeDependency(rdd, 0, pos, rdd.partitions.length)
pos += rdd.partitions.length
}
deps
}
}
窄依赖类。通过代码我们可以发现,上面的 OneToOneDependency 和 RangeDependency 都继承了 NarrowDependency 这个类。现在我们来看下上图的下半部分。
join和cogroup举例:Spark join和cogroup算子-CSDN博客
class CoGroupedRDD[K: ClassTag](
@transient var rdds: Seq[RDD[_ <: Product2[K, _]]],
part: Partitioner)
extends RDD[(K, Array[Iterable[_]])](rdds.head.context, Nil) {
override def getDependencies: Seq[Dependency[_]] = {
rdds.map { rdd: RDD[_] =>
if (rdd.partitioner == Some(part)) {
logDebug("Adding one-to-one dependency with " + rdd)
new OneToOneDependency(rdd)
} else {
logDebug("Adding shuffle dependency with " + rdd)
new ShuffleDependency[K, Any, CoGroupCombiner](
rdd.asInstanceOf[RDD[_ <: Product2[K, _]]], part, serializer)
}
}
}
}
cartesian:笛卡尔积
class CartesianRDD[T: ClassTag, U: ClassTag](
sc: SparkContext,
var rdd1 : RDD[T],
var rdd2 : RDD[U])
extends RDD[(T, U)](sc, Nil)
with Serializable {
override def getDependencies: Seq[Dependency[_]] = List(
new NarrowDependency(rdd1) {
def getParents(id: Int): Seq[Int] = List(id / numPartitionsInRdd2)
},
new NarrowDependency(rdd2) {
def getParents(id: Int): Seq[Int] = List(id % numPartitionsInRdd2)
}
)
}
接下来我们再来看宽依赖。在官方文档中 Spark 2.4.7 ScalaDoc,宽依赖的描述为:
Represents a dependency on the output of a shuffle stage. Note that in the case of shuffle, the RDD is transient since we don’t need it on the executor side.
官方这里并没有从 RDD 分区角度来解释什么是 ShuffleDependency ,只是说需要 shuffle 的两个 Stage 的依赖。那到底什么是 ShuffleDependency 呢?我们来看下图。
看到这可能有的同学会说,这不和 NarrowDependency 一样么?仔细看,NarrowDependency 虽然也有 child RDD 的一个分区依赖 parent RDD 的多个分区的情况,但都是依赖分区的全部。而 ShuffleDependency 中,child RDD 的一个分区依赖的是 parent RDD 中各个分区的某一部分。如上图左半部分,child RDD 的两个分区分别只依赖 parent RDD 中的 1 和 2 部分。而计算出 1 或者 2 部分的过程,以及 child RDD 分别读取 1 和 2 的过程,即为 shuffle write/shuffle read,这个过程正是 shuffle 开销所在。
Spark 之所以要将依赖关系分为 NarrowDependency 和 ShuffleDependency ,是可以更好的将各种依赖类型进行分类,明确数据怎么流出流入,从而更容易生成对应的物理执行计划。
参考:
深入解读 Spark 宽依赖和窄依赖(ShuffleDependency & NarrowDependency)_因特马的博客-CSDN博客
Spark宽依赖和窄依赖深度剖析 - 简书