Spark基础概念02-缓存机制、RDD血缘和依赖关系

一、RDD血缘关系

  • RDD 只支持粗粒度转换,即在大量记录上执行的单个操作。将创建 RDD 的一系列Lineage (血统)记录下来,以便恢复丢失的分区。RDD 的Lineage 会记录RDD 的元数据信息和转换行为,当该RDD 的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的 数据分区。
object Lineage{
    
    
  def main(args: Array[String]): Unit = {
    
    
    val sc = SparkContext.getOrCreate(new SparkConf().setMaster("local[*]").setAppName(this.getClass.getSimpleName))
    val fileRDD = sc.textFile("files/03ages/user-age.txt")
    println(fileRDD.toDebugString)
    //(2) files/03ages/user-age.txt MapPartitionsRDD[1] at textFile at SparkDemo.scala:128 []
    // |  files/03ages/user-age.txt HadoopRDD[0] at textFile at SparkDemo.scala:128 []
    println("---------------------")
    val wordRDD = fileRDD.flatMap(_.split(" "))
    println(wordRDD.toDebugString)
    //(2) MapPartitionsRDD[2] at flatMap at SparkDemo.scala:131 []
    // |  files/03ages/user-age.txt MapPartitionsRDD[1] at textFile at SparkDemo.scala:128 []
    // |  files/03ages/user-age.txt HadoopRDD[0] at textFile at SparkDemo.scala:128 []
    println("---------------------")
    val mapRDD = wordRDD.map((_,1))
    println(mapRDD.toDebugString)
    //(2) MapPartitionsRDD[3] at map at SparkDemo.scala:134 []
    // |  MapPartitionsRDD[2] at flatMap at SparkDemo.scala:131 []
    // |  files/03ages/user-age.txt MapPartitionsRDD[1] at textFile at SparkDemo.scala:128 []
    // |  files/03ages/user-age.txt HadoopRDD[0] at textFile at SparkDemo.scala:128 []

    println("---------------------")
    println(mapRDD.reduceByKey(_ + _).toDebugString)
    //(2) ShuffledRDD[4] at reduceByKey at SparkDemo.scala:147 []
    // +-(2) MapPartitionsRDD[3] at map at SparkDemo.scala:139 []
    //    |  MapPartitionsRDD[2] at flatMap at SparkDemo.scala:133 []
    //    |  files/03ages/user-age.txt MapPartitionsRDD[1] at textFile at SparkDemo.scala:128 []
    //    |  files/03ages/user-age.txt HadoopRDD[0] at textFile at SparkDemo.scala:128 []
  }
}

二、RDD依赖关系

  • 这里所谓的依赖关系,其实就是两个相邻 RDD 之间的关系
  • 窄依赖:表示每一个父(上游)RDD 的 Partition 最多被子(下游)RDD 的一个 Partition 使用, 窄依赖我们形象的比喻为独生子女。
    ➢OnoToOneDependency、RangeDependency extends NarrowDependency
  • 宽依赖:表示同一个父(上游)RDD 的 Partition 被多个子(下游)RDD 的 Partition 依赖,会 引起 Shuffle,宽依赖我们形象的比喻为多生。
    ➢ShuffleDependency
object Dependency{
    
    
  def main(args: Array[String]): Unit = {
    
    
    val sc = SparkContext.getOrCreate(new SparkConf().setMaster("local[*]").setAppName(this.getClass.getSimpleName))


    println(sc.textFile("files/03ages/user-age.txt").dependencies)
    //List(org.apache.spark.OneToOneDependency@fab35b1)
    println("----------------------------")
    println(sc.textFile("files/03ages/user-age.txt").flatMap(_.split(" ")).dependencies)
    //List(org.apache.spark.OneToOneDependency@51c959a4)
    println("----------------------------")
    println(sc.textFile("files/03ages/user-age.txt").flatMap(_.split(" ")).map((_,1)).dependencies)
    //List(org.apache.spark.OneToOneDependency@a66e580)
    println("----------------------------")
    println(sc.textFile("files/03ages/user-age.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).dependencies)
    //List(org.apache.spark.ShuffleDependency@717d7587)
  }
}

三、测试缓存效果

  • 在spark-shell里执行以下代码即可看出效果
object Cache {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val conf = new SparkConf().setMaster("local[2]").setAppName("01")
    val sc = SparkContext.getOrCreate(conf)
    //把文件上传到hdfs
    sc.textFile("D:\\JavaProjects\\ClassStudy\\Scala\\sparkdemo\\files\\02users\\users.csv").saveAsTextFile("hdfs://192.168.221.140:9000/kb10/rddsave2")
    //加载缓存文件cache
    val u1 = sc.textFile("hdfs://192.168.221.140:9000/kb10/rddsave2").cache()
    //使缓存文件生效
    u1.collect().foreach(println(_))
    //删除文件,再次尝试可以发现文件依旧可以输出,在spark-shell里看
  }
}

四、persist缓存

  • cache方法其实是persist方法的一个特例:调用的是无参数的persist(),代表缓存级别是仅内存的情况
  • persist方法有三种:
    ➢persist():默认无参数仅内存级别的
    ➢persist(newLevel):这个方法需要之前对RDD没有设置过缓存级别
    ➢persist(newLevel,allowOverride):这个方法适用于之前对RDD设置过缓存级别,但是想更改缓存级别的情况
  • 取消缓存统一使用unpersist()方法
  • StorageLevel设定有12种缓存策略,可以根据自己的情况选择合适的,一般情况下建议使用persist(StorageLevel.MEMORY_AND_DISK)方法替代cache()方法,以防止内存不足造成程序运行错误
object Persist{
    
    
  def main(args: Array[String]): Unit = {
    
    
    val sc = SparkContext.getOrCreate(new SparkConf().setMaster("local[*]").setAppName(this.getClass.getSimpleName))
    val test = sc.textFile("files/03ages/user-age.txt")

    println(test.toDebugString)
    //(2) files/03ages/user-age.txt MapPartitionsRDD[1] at textFile at SparkDemo.scala:196 []
    // |  files/03ages/user-age.txt HadoopRDD[0] at textFile at SparkDemo.scala:196 []
    println("---------------------")
    println(test.cache().toDebugString)
    //(2) files/03ages/user-age.txt MapPartitionsRDD[1] at textFile at SparkDemo.scala:196 [Memory Deserialized 1x Replicated]
    // |  files/03ages/user-age.txt HadoopRDD[0] at textFile at SparkDemo.scala:196 [Memory Deserialized 1x Replicated]
    println("---------------------")
    println(test.unpersist().persist(StorageLevel.MEMORY_AND_DISK_2).toDebugString)
  }
}

五、缓存容错机制

缓存有可能丢失,或者存储于内存的数据由于内存不足而被删除,RDD 的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。

  • 通过基于RDD 的一系列转换,丢失的数据会被重算,由于RDD 的各个 Partition 是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部Partition。
  • Spark 会自动对一些 Shuffle 操作的中间数据做持久化操作(比如:reduceByKey)。这样 做的目的是为了当一个节点 Shuffle 失败了避免重新计算整个输入。但是,在实际使用的时候,如果想重用数据,仍然建议调用 persist 或 cache。

六、检查点

检查点是通过将RDD 中间结果写入磁盘

  • 由于血缘依赖过长会造成容错成本过高,这样就不如在中间阶段做检查点容错如果检查点之后有节点出现问题,可以从检查点开始重做血缘,减少了开销。
  • 对 RDD 进行 checkpoint 操作并不会马上被执行,必须执行 Action 操作才能触发。
object CheckPoint{
    
    
  def main(args: Array[String]): Unit = {
    
    
    val sc = SparkContext.getOrCreate(new SparkConf().setMaster("local[*]").setAppName(this.getClass.getSimpleName))
    // 设置检查点路径
    sc.setCheckpointDir("files/00checkpoint")
    // 创建一个 RDD,读取指定位置文件:hello atguigu atguigu
    val lineRDD = sc.textFile("files/03ages/user-age.txt")
    // 业务逻辑
    val wordRdd = lineRDD.flatMap(line=>line.split(" "))

    val wordToOneRDD = wordRdd.map(x=>(x,System.currentTimeMillis()))
    // 增加缓存,避免再重新跑一个 job 做 checkpoint
    wordToOneRDD.cache()
    // 数据检查点:针对 wordToOneRdd 做检查点计算
    wordToOneRDD.checkpoint()
    // 触发执行逻辑
    wordToOneRDD.collect().foreach(println)
  }
}

七、缓存和检查点区别

  • Cache 缓存只是将数据保存起来,不切断血缘依赖。Checkpoint 检查点切断血缘依赖。
  • Cache 缓存的数据通常存储在磁盘、内存等地方,可靠性低。Checkpoint 的数据通常存 储在HDFS 等容错、高可用的文件系统,可靠性高。
  • 建议对checkpoint()的 RDD 使用Cache 缓存,这样 checkpoint 的 job 只需从 Cache 缓存 中读取数据即可,否则需要再从头计算一次RDD。

猜你喜欢

转载自blog.csdn.net/xiaoxaoyu/article/details/112278437