Checkpoint源码分析

Checkpoint,是Spark提供的一个比较高级的功能。有的时候啊,比如说,我们的Spark应用程序,特别的复杂,然后呢,从初始的RDD开始,到最后整个应用程序完成,有非常多的步骤,比如超过20个transformation操作。而且呢,整个应用运行的时间也特别长,比如通常要运行1~5个小时。

在上述情况下,就比较适合使用checkpoint功能。因为,对于特别复杂的Spark应用,有很高的风险,会出现某个要反复使用的RDD,因为节点的故障,虽然之前持久化过,但是还是导致数据丢失了。那么也就是说,出现失败的时候,没有容错机制,所以当后面的transformation操作,又要使用到该RDD时,就会发现数据丢失了(CacheManager),此时如果没有进行容错处理的话,那么可能就又要重新计算一次数据。

简而言之,针对上述情况,整个Spark应用程序的容错性很差。

所以,针对上述的复杂Spark应用的问题(没有容错机制的问题)。就可以使用checkponit功能。

checkpoint功能是什么意思?checkpoint就是说,对于一个复杂的RDD chain,我们如果担心中间某些关键的,在后面会反复几次使用的RDD,可能会因为节点的故障,导致持久化数据的丢失,那么就可以针对该RDD格外启动checkpoint机制,实现容错和高可用。

checkpoint,就是说,首先呢,要调用SparkContext的setCheckpointDir()方法,设置一个容错的文件系统的目录,比如说HDFS;然后,对RDD调用调用checkpoint()方法。之后,在RDD所处的job运行结束之后,会启动一个单独的job,来将checkpoint过的RDD的数据写入之前设置的文件系统,进行高可用、容错的类持久化操作。

那么此时,即使在后面使用RDD时,它的持久化的数据,不小心丢失了,但是还是可以从它的checkpoint文件中直接读取其数据,而不需要重新计算。(CacheManager)

1、如何进行checkpoint?
SparkContext.setCheckpointDir()
RDD.checkpoint()
2、Checkpoint原理剖析
3、Checkpoint与持久化的不同:lineage的改变
4、RDD.iterator():读取checkpoint数据
5、给要checkpoint的RDD,先进行persist(StorageLevel.DISK_ONLY)

RDD.iterator
  /**
   * 先persist,再checkpoint
   * 那么首先执行到该rdd的iterator之后,会发现storageLevel != StorageLevel.NONE
   * 就通过CacheManager去获取数据,此时发现通过BlockManager获取不到数据(因为第一次执行)
   * 那么就会第一次还是会计算一次该rdd的数据,然后通过CacheManager的putInBlockManager将其通过
   * BlockManager进行持久化
   * rdd所在的job运行结束了,然后启动单独job进行checkpoint操作,此时是不是又会执行到该rdd的iterator方法
   * 那么就会发现持久化级别不为空,默认从BlockManager直接读取持久化数据(正常情况下可以)但是问题是,如果非正常情况下
   * 持久化数据丢失了,那么此时会走else,调用computeOrReadCheckpoint方法判断如果rdd是isCheckpoint为ture
   * 就会用用它的父rdd的iterator方法,其实就是从checkpoint外部文件系统中读取数据
   */
  
  final def iterator(split: Partition, context: TaskContext): Iterator[T] = {
    
    
    // TODO getOrCompute  如果StorageLevel不为NONE,之前持久化过RDD,那么就不要直接去从父RDD执行算子,计算新的RDD的partition了
    // 优先尝试使用CacheManager,去获取持久化的数据
    if (storageLevel != StorageLevel.NONE) {
    
    
      SparkEnv.get.cacheManager.getOrCompute(this, split, context, storageLevel)
    } else {
    
    
      // TODO 进行rdd partition的计算
      computeOrReadCheckpoint(split, context)
    }
  }
=> computeOrReadCheckpoint
  private[spark] def computeOrReadCheckpoint(split: Partition, context: TaskContext): Iterator[T] =
  {
    
    
    // TODO   MapPartitionsRDD.compute
    if (isCheckpointed) firstParent[T].iterator(split, context) else compute(split, context)
  }
==> CheckpointRDD.compute
  override def compute(split: Partition, context: TaskContext): Iterator[T] = {
    
    
    val file = new Path(checkpointPath, CheckpointRDD.splitIdToFile(split.index))
    CheckpointRDD.readFromFile(file, broadcastedConf, context)
  }
===> CheckpointRDD.readFromFile  读取checkpoint数据
  def readFromFile[T](
      path: Path,
      broadcastedConf: Broadcast[SerializableWritable[Configuration]],
      context: TaskContext
    ): Iterator[T] = {
    
    
    val env = SparkEnv.get
    val fs = path.getFileSystem(broadcastedConf.value.value)
    val bufferSize = env.conf.getInt("spark.buffer.size", 65536)
    val fileInputStream = fs.open(path, bufferSize)
    val serializer = env.serializer.newInstance()
    val deserializeStream = serializer.deserializeStream(fileInputStream)

    // Register an on-task-completion callback to close the input stream.
    context.addTaskCompletionListener(context => deserializeStream.close())

    deserializeStream.asIterator.asInstanceOf[Iterator[T]]
  }

猜你喜欢

转载自blog.csdn.net/m0_46449152/article/details/109562062