概念上来说
Shuffle的含义就是洗牌,将数据打散,父RDD一个分区中的数据如果给了子RDD的多个分区(只要存在这种可能),就是shuffle。Shuffle会有网络传输数据,但是有网络传输,并不意味着就是shuffle。
窄依赖:没有发生shuffle
宽依赖:存在shuffle
也许大家看了上面的说法只是有个初步的印象,下面我将以join为例进行讲解,相信大家看了这个例子就会对上面的概念有一个更为深刻的理解。
val rdd1=sc.parallelize(List((1,"wzh"),(2,"lly")),2)
val rdd2=sc.parallelize(List((1,"wzh"),(2,"lly")),2)
rdd1.join(rdd2).collect
普通的join是一个宽依赖,比如rdd1.join(rdd2).collect中,(2个rdd都通过parallelize并行化创建),在这个过程中会产生3个stage。第一个Stage是rdd1(包括形成rdd1过程所进行的转换操作)将中间数据写到磁盘上等待下游来拉取,第二个Stage是rdd2(包括形成rdd2过程所进行的转换操作)将中间数据写到磁盘上等待下游来拉取,第三个Stage就是下游读取磁盘数据。也产生了2个shuffle。
这里大家也许会困惑,明明2个rdd各有2个分区,4个分区均各有一个数据,在join时并没有出现父rdd的某个分区中的数据跑到子rdd不同分区的情况,也不可能发生(因为单个数据不会拆分),为什么还会出现shuffle?实际上,虽然一个父rdd中的一个分区中的数据并没有被拆开,但是它存在着会发生一个父rdd的一个分区数据分给多个子rdd分区的可能(即不管实际上有多少数据,只要这种模式下存在着这种可能),就会产生shuffle,就是宽依赖,就会划分stage。
相信通过上面的例子,大家应该对何时发生宽依赖有了比较深的理解了。这里大家可能会疑惑,如果是上面这种说法,那么join不就一定是宽依赖吗?其实还存在着一种特殊的join,见下图:
在先进行grouByKey之后,再进行join,且在这个过程中既没有修改RDD分区的数量,也没有设置新的分区器(默认都是按照hashpartitioner分区),那么这个join就是窄依赖。这里稍微详细的解释一下,因为在上面2个RDD中,在groupByKey之后,会产生shuffle,且按照hashpartitioner进行分区,也就是说2个RDD中的数据会按照同样的分区方式进行分区,从而导致相对应的key所对应的数据都到了同一个分区,因此再进行join时,只要子RDD分区数和分区器(默认还是hashpartitioner)不变,这些相对应的数据还是会跑到同一分区中,因此就不会出现父RDD一个分区中的数据跑到子RDD的多个分区中,因此就是一个窄依赖。
注:在这个过程中也可能发生网络传输,但是并没有发生shuffle,因此有网络传输并不一定就是shuffle。
这里对上面所述进行一个通俗的总结,如果在进行join之前调用的是宽依赖(存在shuffle)的RDD,两个RDD的分区数量一致,且join之后产生的子RDD的分区数量也一样,那么这个时候的join就是窄依赖。除此之外的,都是宽依赖。