一、窄依赖与宽依赖剖析
- 在之前的文章中曾对RDD进行剖析,详情看Spark学习之路(三):剖析RDD的概念及用三种方式创建RDD,知道RDD与RDD之间是存在依赖关系(也叫血缘关系)的,每当RDD调用transform算子生成另一个RDD时,这两个RDD之间就存在依赖关系,事实上,还可以对两个RDD之间的依赖关系进行进一步划分,分为窄依赖关系和宽依赖关系。
- 在剖析窄依赖与宽依之前必须了解一个事实,在RDD中存在许多分区,而RDD也有一份分区列表来记录这些分区,比如从HDFS中读取数据时,默认的分区划分就是根据HDFS中的数据块的个数划分,一个数据块即为一个分区。因此可以给出窄依赖的定义:一个RDD中的每一个分区的数据仅仅依赖于父RDD对应分区的数据,即仅仅由父RDD对应分区的数据变换而来,即分区与分区之间是一对一的关系,如下图所示:
宽依赖的定义为:一个RDD中的每一个分区的数据依赖于父RDD多个分区的数据,即父RDD多个分区的数据变换而来,即分区与分区之间是一对多的关系,如下图所示:
以上便是窄依赖和宽依赖的基本概念及原理,事实上,因为宽依赖而导致从被依赖RDD的多个分区拉取数据的过程称之为Shuffle,Shuffle是Spark中非常重要的概念,这也是DAG调度器将job划分Stage的唯一依据,且因为Shuffle往往伴随着大量跨分区的数据传输,其耗费的时间也是最长的,毫不夸张地说Spark程序最花费时间的环节就是Shuffle。现在通过对WordCount程序的窄依赖与宽依赖的划分,可以更清楚地知道WordCount程序的偏底层的数据流向:
二、窄依赖与宽依赖的区别
- 宽依赖往往对应着Shuffle操作,需要在运行的过程中将同一个RDD分区传入到不同的RDD分区中,中间可能涉及到多个节点之间数据的传输,而窄依赖的每个父RDD分区通常只会传入到另一个子RDD分区,通常在一个节点内完成。
- 当窄依赖的子RDD数据丢失时,由于父RDD的一个分区只对应一个子RDD分区,这样只需要重新计算与子RDD分区对应的父RDD分区即可;当宽依赖的子RDD数据丢失时,一个分区的数据通常由自多个父RDD分区数据变换而来,极端情况下,所有父RDD的分区都有可能重新计算,因此计算量很大,需要注意容错,一般而言会在此处设置Checkpoint。
三、窄依赖算子与宽依赖算子
- 在Spark学习之路(四):深度图解Spark算子运作原理中曾剖析过Spark算子的概念及运算原理,其中对Spark算子分成了两类:transform算子和action算子,事实上还可以对transform算子进行更进一步的划分:窄依赖算子和宽依赖算子(Shuffle算子),常用的窄依赖算子如下:
常用的宽依赖算子如下:算子名称 算子功能 map 对RDD中的每个元素都执行一个指定函数来产生一个新的RDD。任何原RDD中的元素在新RDD中都有且只有一个元素与之对应。 flatMap 与map类似,将原RDD中的每个元素通过函数f转换为新的元素,并将这些元素放入一个集合,构成新的RDD。 filter 对RDD元素进行过滤,返回一个新的数据集,由经过给定的过滤条件函数后返回值为true的原元素组成。 union 合并两个数据类型相同的 RDD ,并不进行去重操作,保存所有元素。 distinct 将RDD进行去重操作,返回去重后的RDD。 算子名称 算子功能 cartesian 返回两个RDD的笛卡尔积。 groupBy 将元素通过函数生成相应的 Key,数据就转化为 Key-Value 格式,之后将 Key 相同的元素分为一组。 groupByKey 对RDD中的数据进行分组操作,在一个由(K, V)键值对组成的数据集上调用,返回一个(K, Seq[V])对的数据集。 sortBy 对RDD中的每个元素按照给定的条件进行排序。 sortByKey 对RDD中的每个元素按照Key进行排序。 ReduceByKey 对RDD中的每个元素对按照Key进行聚合操作。 join 对两个RDD中的每个元素对进行join操作,将两个RDD中具有相同的key的元素的value进行笛卡尔积,返回(key,(value1,value2))类型的值。
四、总结
- 由宽依赖而产生的Shuffle是很耗费时间的,从而如何对Shuffle进行调优是一个难点和重点,这也是一个优化程序的点,也可以通过由宽依赖算子生成的RDD进行Checkpoint操作从而避免数据丢失时产生大量的计算也是Spark调优点之一。感谢你的阅读,如有错误请不吝赐教!
- 更多内容请看 萧邦主的技术博客导航