Spark RDD之数据持久化

RDD的数据持久化

​ RDD本身并不存放数据,在没有特殊设置的情况下,每一个动作算子都会导致重新计算一遍数据。即使我们在代码里面看起来好像已经重用了某些算子对象。例如:

object RDDStoreExample {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Spark_RDDStore")
    val sparkContext = new SparkContext(sparkConf)
    val list = List("hello scala", "hello spark")
    val rdd = sparkContext.makeRDD(list)
    val flatRDD = rdd.flatMap(_.split(" "))
    val mapRDD = flatRDD.map(item => {
      println("@@@@@@@@")
      (item, 1)
    })
    val reduceRDD = mapRDD.reduceByKey(_ + _)
    reduceRDD.collect().foreach(println)
    println()
    val groupRDD = mapRDD.groupByKey()
    groupRDD.collect().foreach(println)
    sparkContext.stop()
  }
}

运行结果:

@@@@@@@@
@@@@@@@@
@@@@@@@@
@@@@@@@@
(spark,1)
(scala,1)
(hello,2)

@@@@@@@@
@@@@@@@@
@@@@@@@@
@@@@@@@@
(spark,CompactBuffer(1))
(scala,CompactBuffer(1))
(hello,CompactBuffer(1, 1))

​ 通过运行结果发现,即使已经复用了mapRDD对象,但是数据流还是在进行从头开始的计算。为了避免这种情况,我们可以设置RDD缓存。

1:代码设置

    mapRDD.cache()
	  // or
    mapRDD.persist(StorageLevel.DISK_ONLY)

​ cache的底层采用的依然是persist。但是默认存储的级别是MEMORY_ONLY.并且持久化之后的数据在程序运行结束之后会被删除。如果想要长久保存,应该设置检查点。检查点设置可参考下文。

2:cache()方法解析

​ cache()方法会调用persist方法,persist方法首先会判断该RDD是否被设置为了检查点。如果被设置了检查点,则执行转换检查点存储级别的操作,但是这一步是一定会抛出异常的,具体原因见persist的源码。

  def persist(newLevel: StorageLevel): this.type = {
    if (isLocallyCheckpointed) {
      // This means the user previously called localCheckpoint(), which should have already
      // marked this RDD for persisting. Here we should override the old storage level with
      // one that is explicitly requested by the user (after adapting it to use disk).
      persist(LocalRDDCheckpointData.transformStorageLevel(newLevel), allowOverride = true)
    } else {
      persist(newLevel, allowOverride = false)
    }
  }
isLocallyCheckpointed方法:
  /**
   * Return whether this RDD is marked for local checkpointing.
   * 返回此RDD是否标记为本地检查点。
   * Exposed for testing.
   * 暴露出来进行测试。
   */
  private def isLocallyCheckpointed: Boolean = {
    checkpointData match {
      case Some(_: LocalRDDCheckpointData[T]) => true
      case _ => false
    }
  }

checkpointData类型:

private[spark] var checkpointData: Option[RDDCheckpointData[T]] = None

persist(newLevel: StorageLevel, allowOverride: Boolean)实现
  /**
   * Mark this RDD for persisting using the specified level.
   * 将此RDD标记为使用指定级别进行持久化
   * @param newLevel 新的存储级别
   * @param allowOverride 是否用新存储级别替代已经存在的存储级别
   */
  private var storageLevel: StorageLevel = StorageLevel.NONE
  private def persist(newLevel: StorageLevel, allowOverride: Boolean): this.type = {
    // 如果默认的存储级别不是None,或者新的存储级别不是None,或者允许覆盖之前的存储级别
    // 以上三种情况都会报错
    // 此处可以解释如果rdd已经设置了检查点此处的allowOverride变成false,此处一定会报错。
    if (storageLevel != StorageLevel.NONE && newLevel != storageLevel && !allowOverride) {
      throw new UnsupportedOperationException(
        "Cannot change storage level of an RDD after it was already assigned a level")
    }
    // 如果这是第一次将此RDD标记为持久化,请注册它使用SparkContext进行清理和核算。只做一次。
    if (storageLevel == StorageLevel.NONE) {
      sc.cleaner.foreach(_.registerRDDForCleanup(this))
      // 持久化数据
      sc.persistRDD(this)
    }
    storageLevel = newLevel
    this
  }

缓存rdd

  /**
   * Register an RDD to be persisted in memory and/or disk storage
   */
  private[spark] def persistRDD(rdd: RDD[_]) {
    persistentRdds(rdd.id) = rdd
  }

追踪rdd

  // Keeps track of all persisted RDDs
  // 跟踪所有持久化的rdd
  private[spark] val persistentRdds = {
    val map: ConcurrentMap[Int, RDD[_]] = new MapMaker().weakValues().makeMap[Int, RDD[_]]()
    map.asScala
  }

3:persist

  /**
   * Set this RDD's storage level to persist its values across operations after the first time
   * it is computed. This can only be used to assign a new storage level if the RDD does not
   * have a storage level set yet. Local checkpointing is an exception.
   */
  def persist(newLevel: StorageLevel): this.type = {
    if (isLocallyCheckpointed) {
      // This means the user previously called localCheckpoint(), which should have already
      // marked this RDD for persisting. Here we should override the old storage level with
      // one that is explicitly requested by the user (after adapting it to use disk).
      persist(LocalRDDCheckpointData.transformStorageLevel(newLevel), allowOverride = true)
    } else {
      persist(newLevel, allowOverride = false)
    }
  }

接下来就和之前cache一样。

4:checkPoint()

首先要设置缓存路径SparkContext.setCheckpointDir

  /**
   * Mark this RDD for checkpointing. It will be saved to a file inside the checkpoint
   * 将此RDD标记为检查点进行落盘,数据将会保存在前面通过SparkContext.setCheckpointDir设置的目录下,
   * directory set with `SparkContext#setCheckpointDir` and all references to its parent
   * 关于此RDD的所有血缘关系将会被移除,checkpoint的操作会在当前rdd上的所有算子被运算完成之后才被调用,
   * RDDs will be removed. This function must be called before any job has been
   * 强烈建议在RDD调用checkpoint之前首先调用persist将数据缓存进内存,否则此RDD将会被重复计算。
   * executed on this RDD. It is strongly recommended that this RDD is persisted in
   * memory, otherwise saving it on a file will require recomputation.
   */
  def checkpoint(): Unit = RDDCheckpointData.synchronized {
    // NOTE: we use a global lock here due to complexities downstream with ensuring
    // 注意:由于下游的复杂性,我们在这里使用全局锁,以确保子RDD分区指向正确的父分区。
    // children RDD partitions point to the correct parent partitions. In the future
    // 今后我们应该重新考虑这个问题。
    // we should revisit this consideration.
    if (context.checkpointDir.isEmpty) {
      throw new SparkException("Checkpoint directory has not been set in the SparkContext")
    } else if (checkpointData.isEmpty) {
      checkpointData = Some(new ReliableRDDCheckpointData(this))
    }
  }

持久化和检查点的区别:

在Spark的持久化使用中,我们会将一些经常使用到的数据进行持久化,比如使用cache()或者persist()方法进行持久化操作,但是当某个节点或者executor挂掉之后,持久化的数据会丢失,因为我们的数据是保存在内存当中的,这时就会重新计算RDD,如果某个之前的RDD需要大量的计算时间,这时将会浪费很多时间,因此,我们有时候需要使用checkpoint操作来将一些数据持久化可容错文件系统中,比如HDFS文件系统中,虽然这种方式可能对性能带来了一定的影响(磁盘IO),但是为了避免大量的重复计算操作,有时也可以使用性能代价来换取时间效率上的提升。
当我们对某个RDD进行了缓存操作之后,首先会去CacheManager中去找,然后紧接着去BlockManager中去获取内存或者磁盘中缓存的数据,如果没有进行缓存或者缓存丢失,那么就会去checkpoint的容错文件系统中查找数据,如果最终没有找到,那就会按照RDD血缘重新计算。cache 和 checkpoint 之间有一个重大的区别,cache 将 RDD以及RDD的血统(记录了这个RDD如何产生)缓存到内存中,当缓存的RDD失效的时候(如内存损坏),它们可以通过血统重新计算来进行恢复。但是 checkpoint 将 RDD 缓存到了 HDFS 中,同时忽略了它的血统(也就是RDD之前的那些依赖)。为什么要丢掉依赖?因为可以利用 HDFS 多副本特性保证容错!
在进行 checkpoint 前,要先对 RDD 进行 cache。原因是:checkpoint 会等到 job 结束后另外启动专门的 job 去完成 checkpoint,也就是说需要 checkpoint 的 RDD 会被计算两次。

persist和 checkpoint之间的区别

persist()可以将 RDD 的partition持久化到磁盘,但该 partition 由 blockManager 管理。一旦 driver program 执行结束,也就是 executor 所在进程 CoarseGrainedExecutorBackend stop,blockManager 也会 stop,被 cache 到磁盘上的 RDD 也会被清空(整个 blockManager 使用的 local 文件夹被删除)。而 checkpoint 将 RDD 持久化到 HDFS 或本地文件夹,如果不被手动 remove 掉,是一直存在的,也就是说可以被下一个 driver program 使用,而 cached RDD 不能被其他 dirver program 使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值