存储级别和存储调用

下面是StorageLevel类的代码解释


/**
 * :: DeveloperApi ::
 * Flags for controlling the storage of an RDD. Each StorageLevel records whether to use memory,
 * or ExternalBlockStore, whether to drop the RDD to disk if it falls out of memory or
 * ExternalBlockStore, whether to keep the data in memory in a serialized format, and whether
 * to replicate the RDD partitions on multiple nodes.
  *
  * 用于控制RDD存储的标志,每个StorageLevel记录是否使用内存或ExternalBlockStore,如果它脱离内存或ExternalBlockStore,
  * 是否将RDD丢弃到磁盘,是否将数据保存在内存中的序列化格式,以及是否复制多个节点上的RDD分区
 *
 * The [org.apache.spark.storage.StorageLevel$]singleton object contains some static constants
 * for commonly useful storage levels. To create your own storage level object, use the
 * factory method of the singleton object (`StorageLevel(...)`).
  * [org.apache.spark.storage.StorageLevel $] singleton对象包含一些常用的存储级别的静态常量,
  * 要创建自己的存储级别对象,请使用单例对象(`StorageLevel(...)`)的工厂方法
 */

StorageLevel的成员如下:

    private var _useDisk: Boolean,
    private var _useMemory: Boolean,
    private var _useOffHeap: Boolean,//使用扩展存储
    private var _deserialized: Boolean,
    private var _replication: Int = 1) //默认的复本数是1

StorageLevel中的Object方法解释如下:

object StorageLevel {
  //不会保存任务数据
  val NONE = new StorageLevel(false, false, false, false)
  //直接将RDD的partition保存在该节点的Disk上
  val DISK_ONLY = new StorageLevel(true, false, false, false)
  //直接将RDD的partition保存在该节点的Disk上,在其他节点上保存一个相同的备份
  val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
  //将RDD的partition对应的原生的Java Object保存在JVM中,如果RDD太大导致它的部分partition不能存储在内存中
  //那么这些partition将不会缓存,并且需要的时候被重新计算,默认缓存的级别
  val MEMORY_ONLY = new StorageLevel(false, true, false, true)
  //将RDD的partition对应的原生的Java Object保存在JVM中,在其他节点上保存一个相同的备份
  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)
  //将RDD的partition反序列化后的对象存储在JVM中,如果RDD太大导致它的部分partition不能存储在内存中
  //超出的partition将被保存在Disk上,并且在需要时读取
  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)
  //将RDD的partition序列化后存储在Tachyon中
  val OFF_HEAP = new StorageLevel(false, false, true, false)

下面是我们的RDD存储的调用
RDD和Block关系如下:
一个RDD有多个Partition,每个Partition对应一个Block块,也就是说一个RDD有多个Block,同时每个BlockId又有拥有唯一的编号BlockId,对应的数据块的编号规则为:“rdd_”+”rddid_”+”splitIndex”,其中这个splitIndex对应的是数据块对应的Partition的序列号

RDD的存储过程分析如下:

  /**
   * Mark this RDD for persisting using the specified level.
    * 标记此RDD以使用指定的级别进行持久化
   * this.type表示当前对象(this)的类型。this指代当前的对象。
   * this.type被用于变量,函数参数和函数返回值的类型声明
   * @param newLevel the target storage level
   * @param allowOverride whether to override any existing level with the new one
   */
  private def persist(newLevel: StorageLevel, allowOverride: Boolean): this.type = {
    // TODO: Handle changes of StorageLevel
    if (storageLevel != StorageLevel.NONE && newLevel != storageLevel && !allowOverride) {
      throw new UnsupportedOperationException(
        "Cannot change storage level of an RDD after it was already assigned a level")
    }
    // If this is the first time this RDD is marked for persisting, register it
    // with the SparkContext for cleanups and accounting. Do this only once.
    //如果这是RDD第一次标记为持久性,请注册与SparkContext进行清理和计费,只做一次
    if (storageLevel == StorageLevel.NONE) {
      sc.cleaner.foreach(_.registerRDDForCleanup(this))
      sc.persistRDD(this)
    }
    storageLevel = newLevel
    this
  }

  /**
   * 设置RDD存储级别在操作之后完成,这里只能分配RDD尚未确认的新存储级别,检查点是一个例别
   * 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.
    * 设置此RDD的存储级别,以便在第一次操作之后保持其值计算,如果RDD没有,这只能用于分配新的存储级别
    *还有一个存储级别,本地检查点是一个例外。
   */
  def persist(newLevel: StorageLevel): this.type = {
    if (isLocallyCheckpointed) {
      //之前已经调用过localCheckpoint(),这里应该标记RDD待久化,在这里我们应该重写旧的存储级别,一个是由用户显式请求
      // 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)
    }
  }

  /** 
   *  Persist this RDD with the default storage level (`MEMORY_ONLY`). 
   *  持久化RDD,默认存储级别MEMORY_ONLY,内存存储
   *  */
  def persist(): this.type = persist(StorageLevel.MEMORY_ONLY)

  /** 
   *  Persist this RDD with the default storage level (`MEMORY_ONLY`). 
   *  持久化RDD使用默认存储级别(内存存储)
   *  */
  def cache(): this.type = persist()

  /**
   * Mark the RDD as non-persistent, and remove all blocks for it from memory and disk.
   * 删除持久化RDD,同时删除内存和硬盘
   * @param blocking Whether to block until all blocks are deleted.
   * @return This RDD.
   */
  def unpersist(blocking: Boolean = true): this.type = {
    logInfo("Removing RDD " + id + " from persistence list")
    sc.unpersistRDD(id, blocking)
    storageLevel = StorageLevel.NONE  //将存储级别设置为None
    this
  }

  /** 
   *  Get the RDD's current storage level, or StorageLevel.NONE if none is set.
   *  获得RDD当前的存储级别 
   *  */
  def getStorageLevel: StorageLevel = storageLevel

下面是我们的RDD的Iterator方法的解析如下:

  /**
   * Task的执行起点,计算由此开始
   * Internal method to this RDD; will read from cache if applicable(可用), or otherwise(否则) compute it.
   * This should ''not'' be called by users directly, but is available for implementors of custom
   * subclasses of RDD.
   * RDD内部方法,从缓存中读取可用,如果没有则计算它,
   */
    final def iterator(split: Partition, context: TaskContext): Iterator[T] = {
      //如果存储级别不是NONE 那么先检查是否有缓存,没有缓存则要进行计算
    if (storageLevel != StorageLevel.NONE) {
      getOrCompute(split, context)
      //SparkEnv包含运行时节点所需要的环境信息
      //cacheManager负责调用BlockManager来管理RDD的缓存,如果当前RDD原来计算过并且把结果缓存起来.
      //接下来的运行都可以通过BlockManager来直接读取缓存后返回
    } else {
    //如果没有缓存,存在检查点时直接获取中间结果
      computeOrReadCheckpoint(split, context)
    }
  }

getOrComputey方法如下:

  /** Gets or computes an RDD partition. Used by RDD.iterator() when an RDD is cached. 
   *  获取或计算一个RDD的分区,当RDD被缓存时由RDD.iterator()使用
   *  */

  private[spark] def getOrCompute(partition: Partition, context: TaskContext): Iterator[T] = {
    //通过RDD的编号和Partition的序号获取到数据块Block的编号
    val blockId = RDDBlockId(id, partition.index)
    var readCachedBlock = true
    // This method is called on executors, so we need call SparkEnv.get instead of sc.env
    //因为这个方法是由executor来调用这个方法,所以可以使用SparkEnv代替sc.env
    //先根据数据块BlockId来读取数据,然后更新数据,这个方法是读写数据的入口
    SparkEnv.get.blockManager.getOrElseUpdate(blockId, storageLevel, elementClassTag, 
    //如果是数据块不存在,则尝试读取检查点的结果进行迭代计算
    () => {
      readCachedBlock = false
      computeOrReadCheckpoint(partition, context)
    }) match {
      case Left(blockResult) =>
        if (readCachedBlock) {
          val existingMetrics = context.taskMetrics().inputMetrics
          existingMetrics.incBytesRead(blockResult.bytes)
          new InterruptibleIterator[T](context, blockResult.data.asInstanceOf[Iterator[T]]) {
            override def next(): T = {
              existingMetrics.incRecordsRead(1)
              delegate.next()
            }
          }
        } else {
          new InterruptibleIterator(context, blockResult.data.asInstanceOf[Iterator[T]])
        }
      case Right(iter) =>
        new InterruptibleIterator(context, iter.asInstanceOf[Iterator[T]])
    }
  }

下面是我们的getOrElseUpdate方法的解析如下:

  /**
   *这个方法是我们的Spark存写数据的入口点,同时这个方法由我们的Executor调用
   * Retrieve the given block if it exists, otherwise call the provided `makeIterator` method
   * to compute the block, persist it, and return its values.
   *
   * @return either a BlockResult if the block was successfully cached, or an iterator if the block
   *         could not be cached.
   */
  def getOrElseUpdate[T](
      blockId: BlockId,
      level: StorageLevel,
      classTag: ClassTag[T],
      makeIterator: () => Iterator[T]): Either[BlockResult, Iterator[T]] = {
    // Attempt to read the block from local or remote storage. If it's present, then we don't need
    // to go through the local-get-or-put path.
    //读取数据的入口点,尝试从本地或者远程读取数据
    get[T](blockId)(classTag) match {
      case Some(block) =>
        return Left(block)
      case _ =>
        // Need to compute the block.
    }
    // Initially we hold no locks on this block.
    //写数据入口
    doPutIterator(blockId, makeIterator, level, classTag, keepReadLock = true) match {
      case None =>
        // doPut() didn't hand work back to us, so the block already existed or was successfully
        // stored. Therefore, we now hold a read lock on the block.
        val blockResult = getLocalValues(blockId).getOrElse {
          // Since we held a read lock between the doPut() and get() calls, the block should not
          // have been evicted, so get() not returning the block indicates some internal error.
          releaseLock(blockId)
          throw new SparkException(s"get() failed for block $blockId even though we held a lock")
        }
        // We already hold a read lock on the block from the doPut() call and getLocalValues()
        // acquires the lock again, so we need to call releaseLock() here so that the net number
        // of lock acquisitions is 1 (since the caller will only call release() once).
        releaseLock(blockId)
        Left(blockResult)
      case Some(iter) =>
        // The put failed, likely because the data was too large to fit in memory and could not be
        // dropped to disk. Therefore, we need to pass the input iterator back to the caller so
        // that they can decide what to do with the values (e.g. process them without caching).
       Right(iter)
    }
  }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章