Spark检查点checkpoint和缓存

一、缓存

RDD通过persist方法或cache方法可以将计算结果缓存,默认情况下 persist() 会把数据以序列化的形式缓存在 JVM 的堆空 间中。 但是并不是这两个方法被调用时立即缓存,而是触发后面的action时,该RDD将会被缓存在计算节点的内存中,并供后面重用。

/**
 * Persist this RDD with the default storage level (`MEMORY_ONLY`).
 */
def persist(): this.type = persist(StorageLevel.MEMORY_ONLY)

/**
 * Persist this RDD with the default storage level (`MEMORY_ONLY`).
 */
def cache(): this.type = persist()

1、缓存的存储级别如下所示

object StorageLevel {
  val NONE = new StorageLevel(false, false, false, false)
  val DISK_ONLY = new StorageLevel(true, false, false, false)
  val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
  val MEMORY_ONLY = new StorageLevel(false, true, false, true)
  val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
  val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
  val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
  val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
  val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
  val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
  val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
  val OFF_HEAP = new StorageLevel(true, true, true, false, 1)

在存储级别的末尾加上“_2”来把持久化数据存为两份

2、spark程序未加入缓存

def main(args: Array[String]): Unit = {
  val sparkConf=new SparkConf().setMaster("local[*]").setAppName(scalaTest.getClass.getSimpleName)
  val sparkContext=new SparkContext(sparkConf)
  val rdd=sparkContext.makeRDD(List((1,1),(2,1),(3,1),(4,1)))
  val resultRdd=rdd.reduceByKey(_+_)
  resultRdd.collect().foreach(println(_))
  //输出血缘关系
  println(resultRdd.toDebugString)
}

输出结果:

(1,1)
(2,1)
(3,1)
(4,1)
(8) ShuffledRDD[1] at reduceByKey at scalaTest.scala:11 []
 +-(8) ParallelCollectionRDD[0] at makeRDD at scalaTest.scala:10 []

3、当加入缓存

def main(args: Array[String]): Unit = {
  val sparkConf=new SparkConf().setMaster("local[*]").setAppName(scalaTest.getClass.getSimpleName)
  val sparkContext=new SparkContext(sparkConf)
  val rdd=sparkContext.makeRDD(List((1,1),(2,1),(3,1),(4,1)))
  val resultRdd=rdd.reduceByKey(_+_)
  //加入缓存
  resultRdd.cache()
  resultRdd.collect().foreach(println(_))
  //输出血缘关系
  println(resultRdd.toDebugString)
}

输出结果多出了 CachedPartitions:

(1,1)
(2,1)
(3,1)
(4,1)
(8) ShuffledRDD[1] at reduceByKey at scalaTest.scala:11 [Memory Deserialized 1x Replicated]
 |       CachedPartitions: 8; MemorySize: 368.0 B; ExternalBlockStoreSize: 0.0 B; DiskSize: 0.0 B
 +-(8) ParallelCollectionRDD[0] at makeRDD at scalaTest.scala:10 [Memory Deserialized 1x Replicated]

二、检查点

Spark中对于数据的保存除了持久化操作之外,还提供了一种检查点的机制,检查点(本质是通过将RDD写入Disk做检查点)是为了通过lineage做容错的辅助,lineage过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果之后有节点出现问题而丢失分区,从做检查点的RDD开始重做Lineage,就会减少开销。检查点通过将数据写入到HDFS文件系统实现了RDD的检查点功能。

扫描二维码关注公众号,回复: 8837813 查看本文章

1、适合使用检查点机制的场景:

(1)DAG中的Lineage过长,如果重算,则开销太大

(2)在宽依赖上做Checkpoint获得的收益更大

2、在程序中加入检查点:

输出结果中ParallelCollectionRDD没有了,变成ReliableCheckpointRDD:

def main(args: Array[String]): Unit = {
  val sparkConf=new SparkConf().setMaster("local[*]").setAppName(scalaTest.getClass.getSimpleName)
  val sparkContext=new SparkContext(sparkConf)
  //测试使用本地目录,生产环境是配置hdfs目录
  sparkContext.setCheckpointDir("D:\\temp\\spark\\checkpoint")
  val rdd=sparkContext.makeRDD(List((1,1),(2,1),(3,1),(4,1)))
  val resultRdd=rdd.reduceByKey(_+_)
  resultRdd.cache()
  resultRdd.checkpoint()
  resultRdd.collect().foreach(println(_))
  //输出血缘关系
  println(resultRdd.toDebugString)
}

(1,1)
(2,1)
(3,1)
(4,1)
(8) ShuffledRDD[1] at reduceByKey at scalaTest.scala:13 []
 |  ReliableCheckpointRDD[2] at collect at scalaTest.scala:15 []

为RDD设置检查点生成的检查点文件是二进制的文件,该RDD的所有依赖于父RDD中的信息将全部被移出。对RDD进行checkpoint操作并不会马上被执行,必须执行Action操作才能触发。

3、checkpoint写流程

(1)在所有 job action 的时候, runJob 方法中都会调用 rdd.doCheckpoint ,  这个会向前递归调用所有的依赖的RDD, 看看需不需要  checkpoint。 需要需要 checkpoint, 然后调用  checkpointData.get.checkpoint(), 里面标记 状态为 CheckpointingInProgress,  里面调用具体实现类的 ReliableRDDCheckpointData 的 doCheckpoint 方法。

(2)doCheckpoint -> writeRDDToCheckpointDirectory, 注意这里会把 job 再运行一次, 如果已经cache 了,就可以直接使用缓存中的 RDD 了, 就不需要重头计算一遍了,  这时候直接把RDD, 输出到 hdfs, 每个分区一个文件, 会先写到一个临时文件, 如果全部输出完,进行 rename , 如果输出失败,就回滚delete。

(3)标记 状态为 Checkpointed, markCheckpointed方法中清除所有的依赖, 怎么清除依赖的呢, 就是 吧RDD 变量的强引用 设置为 null, 垃圾回收了,会触发 ContextCleaner 里面监听清除实际 BlockManager 缓存中的数据

4、checkpoint读流程

如果 一个 RDD 被checkpoint了,  那么这个 RDD 中对分区和依赖的处理都是使用的 RDD 内部的 checkpointRDD 变量, 具体实现是  ReliableCheckpointRDD 类型。 这个是在 checkpoint 写流程中创建的。依赖和获取分区方法中先判断是否已经checkpoint, 如果已经checkpoint了, 就斩断依赖,  使用ReliableCheckpointRDD, 来处理依赖和获取分区。


三、检查点和缓存的区别

缓存把 RDD 计算出来然后放在内存中,但是RDD 的依赖链也不能丢掉, 当某个 executor 宕了,上面cache 的RDD就会丢掉, 需要通过 依赖链重放计算出来

checkpoint 是把 RDD 保存在 HDFS中, 是多副本可靠存储,所以依赖链就可以丢掉了,就斩断了依赖链, 是通过复制实现的高容错。

发布了59 篇原创文章 · 获赞 2 · 访问量 2038

猜你喜欢

转载自blog.csdn.net/zuodaoyong/article/details/103847838