spark封神之路(10)-RDD依赖和血缘

1 依赖和血缘关系

RDD只支持粗粒度转换,即在大量记录上执行的单个操作。将创建RDD的一系列Lineage(血统)记录下来,以便恢复丢失的分区。RDD的Lineage会记录RDD的元数据信息和转换行为,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。(由于RDD中是不记录数据的,为了实现分布式计算中的容错 , RDD必须记录RDD之间的血缘关系)

0

  • RDD之间的依赖关系

相邻的两个RDD之间的依赖关系就是RDD的依赖关系 , RDD之间的依赖关系有两种 一种是窄依赖,一种是宽依赖

  • 展示依赖血缘关系
def main(args: Array[String]): Unit = {
   val sc: SparkContext = SparkUtil.getSc
   val rdd1: RDD[String] = sc.textFile("d://word.txt" , 1)
   // dedug 打印RDD的依赖关心
   println("rdd1:"+rdd1.toDebugString)
   val rdd2: RDD[String] = rdd1.flatMap(_.split("\\s+"))
   println("rdd2:"+rdd2.toDebugString)
   val rdd3: RDD[(String, Int)] = rdd2.map((_, 1))
   println("rdd3:"+rdd3.toDebugString)
   val rdd4: RDD[(String, Int)] = rdd3.reduceByKey(_ + _)
   println("rdd4:"+rdd4.toDebugString)
   rdd4.collect()
   sc.stop()
}

rdd1:(1) d://word.txt MapPartitionsRDD[1] at textFile at WordCount.scala:15 []

|  d://word.txt HadoopRDD[0] at textFile at WordCount.scala:15 []

rdd2:(1) MapPartitionsRDD[2] at flatMap at WordCount.scala:17 []

|  d://word.txt MapPartitionsRDD[1] at textFile at WordCount.scala:15 []

|  d://word.txt HadoopRDD[0] at textFile at WordCount.scala:15 []

rdd3:(1) MapPartitionsRDD[3] at map at WordCount.scala:19 []

|  MapPartitionsRDD[2] at flatMap at WordCount.scala:17 []

|  d://word.txt MapPartitionsRDD[1] at textFile at WordCount.scala:15 []

|  d://word.txt HadoopRDD[0] at textFile at WordCount.scala:15 []

rdd4:(1) ShuffledRDD[4] at reduceByKey at WordCount.scala:21 []

+-(1) MapPartitionsRDD[3] at map at WordCount.scala:19 []

   |  MapPartitionsRDD[2] at flatMap at WordCount.scala:17 []

   |  d://word.txt MapPartitionsRDD[1] at textFile at WordCount.scala:15 []

   |  d://word.txt HadoopRDD[0] at textFile at WordCount.scala:15 []

Process finished with exit code 0

2 RDD依赖关系

1) 窄依赖

 窄依赖:表示每一个父(上游)RDD 的Partition最多被子(下游)RDD的一个 Partition使用,,例如map、filter、union等操作都会产生窄依赖;

单RDD调用某些转换算子,前面RDD分区的数据还在一个分区中 , map() filter()等

  • 从父RDD角度看:一个父RDD只被一个子RDD分区使用。父RDD的每个分区最多只能被一个Child RDD的一个分区使用
  • 从子RDD角度看: 依赖上级RDD的部分分区,精确知道依赖的上级RDD分区,会选择和自己在同一节点的上级RDD分区,没有网络IO开销,高效。如map,flatmap,filter
class OneToOneDependency[T](rdd: RDD[T]) extends NarrowDependency[T](rdd) {
 override def getParents(partitionId: Int): List[Int] = List(partitionId)
}

0

(RDD和RDD)调用某些算子 union() 算子 ,分区数量是两个RDD之和!

0

(RDD和RDD)调用某些算子 join() 算子 ,分区数量是两个RDD之和!

0

2) 宽依赖

宽依赖表示同一个父(上游》RDD 的Partition被多个子(下游)RDD的Partition依赖,会引起Shuffle ,:宽依赖我们形象的比喻为多生。例如groupByKey、reduceByKey、sortByKey等操作都会产生宽依赖;发生了数据重新分发现象!

需要特别说明的是对join操作有两种情况:如果两个RDD在进行join操作时,一个RDD的partition仅仅和另一个RDD中已知个数的Partition进行join,那么这种类型的join操作就是窄依赖,例如图1中左半部分的join操作(join with inputs co-partitioned);其它情况的join操作就是宽依赖,例如图1中右半部分的join操作(join with inputs not co-partitioned),由于是需要父RDD的所有partition进行join的转换,这就涉及到了shuffle,因此这种类型的join操作也是宽依赖。

  • 从父RDD角度看:一个父RDD被多个子RDD分区使用。父RDD的每个分区可以被多个Child RDD分区依赖
  • 从子RDD角度看:依赖上级RDD的所有分区 无法精确定位依赖的上级RDD分区,相当于依赖所有分区(例如reduceByKey) 计算就涉及到节点间网络传输需要shuffle
@DeveloperApi

class ShuffleDependency[K: ClassTag, V: ClassTag, C: ClassTag](

   @transient private val _rdd: RDD[_ Product2[K, V]],

   val partitioner: Partitioner,

   val serializer: Serializer = SparkEnv.get.serializer,

   val keyOrdering: Option[Ordering[K]] = None,

   val aggregator: Option[Aggregator[K, V, C]] = None,

   val mapSideCombine: Boolean = false,

   val shuffleWriterProcessor: ShuffleWriteProcessor = new ShuffleWriteProcessor)

 extends Dependency[Product2[K, V]] {
   
   

0

总结:在这里我们是从父RDD的partition被使用的个数来定义窄依赖和宽依赖,因此可以用一句话概括下:如果父RDD的一个Partition被子RDD的一个Partition所使用就是窄依赖,否则的话就是宽依赖。因为是确定的partition数量的依赖关系,所以RDD之间的依赖关系就是窄依赖;由此我们可以得出一个推论:即窄依赖不仅包含一对一的窄依赖,还包含一对固定个数的窄依赖。

  一对固定个数的窄依赖的理解:即子RDD的partition对父RDD依赖的Partition的数量不会随着RDD数据规模的改变而改变;换句话说,无论是有100T的数据量还是1P的数据量,在窄依赖中,子RDD所依赖的父RDD的partition的个数是确定的,而宽依赖是shuffle级别的,数据量越大,那么子RDD所依赖的父RDD的个数就越多,从而子RDD所依赖的父RDD的partition的个数也会变得越来越多。

数据在没有分区的时候 ,任务可以并行的处理 ,如果数据在处理的过程中需要shuffle ,那么下游的数据就需要从上游中获取数据

1) 一定是要获取所有的数据 , 有可能需要等待上游数据的处理完毕

2) 需要网络之间的数据传输, 占用带宽

3) 某些情况下需要重新排序排序

猜你喜欢

转载自blog.csdn.net/qq_37933018/article/details/118084200