Análisis de código fuente de CacheManager

RDD.iterator

  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)
    }
  }

cacheManager.getOrCompute

  def getOrCompute[T](
      rdd: RDD[T],
      partition: Partition,
      context: TaskContext,
      storageLevel: StorageLevel): Iterator[T] = {
    
    

    val key = RDDBlockId(rdd.id, partition.index)
    logDebug(s"Looking for partition $key")
    // TODO 调用BlockManager的get方法获取数据
    blockManager.get(key) match {
    
    
      case Some(blockResult) =>
        // Partition is already materialized, so just return its values
        val inputMetrics = blockResult.inputMetrics
        val existingMetrics = context.taskMetrics
          .getInputMetricsForReadMethod(inputMetrics.readMethod)
        existingMetrics.incBytesRead(inputMetrics.bytesRead)

        val iter = blockResult.data.asInstanceOf[Iterator[T]]
        new InterruptibleIterator[T](context, iter) {
    
    
          override def next(): T = {
    
    
            existingMetrics.incRecordsRead(1)
            delegate.next()
          }
        }
        /**
         * 如果从blockManager没有获取到数据,虽然rdd持久化过,但是因为未知的原因,
         * 数据既不在本地内存或磁盘,也不在远程blockmanager的本地或磁盘,就要就行后续处理
         */
      case None =>
        // Acquire a lock for loading this partition
        // If another thread already holds the lock, wait for it to finish return its results
        
        // 再调用一次BlockManager的get()方法 去获取数据
        val storedValues = acquireLockForPartition[T](key)
        if (storedValues.isDefined) {
    
    
          return new InterruptibleIterator[T](context, storedValues.get)
        }

        // Otherwise, we have to load the partition ourselves
        try {
    
    
          logInfo(s"Partition $key not found, computing it")

          /**
           * TODO
           *  如果rdd之前checkpint过,那么就尝试读取他的checkpoint
           *  但是如果rdd没有checkpint过,只能重新使用父rdd的数据,执行算子计算一份
           */
          val computedValues = rdd.computeOrReadCheckpoint(partition, context)

          // If the task is running locally, do not persist the result
          if (context.isRunningLocally) {
    
    
            return computedValues
          }

          // Otherwise, cache the values and keep track of any updates in block statuses
          val updatedBlocks = new ArrayBuffer[(BlockId, BlockStatus)]

          /**
           *  TODO
           *  由于走cachemanager ,肯定意味着rdd是设置持久化级别的
           *  因为某些原因持久化的数据没有找到,那么才会走到这里
           *  所以读取了checkpoint数据或者重新计算后要用putInBlockManager
           *  将数据在blockmanager中持久化一份
           */
          val cachedValues = putInBlockManager(key, computedValues, storageLevel, updatedBlocks)
          val metrics = context.taskMetrics
          val lastUpdatedBlocks = metrics.updatedBlocks.getOrElse(Seq[(BlockId, BlockStatus)]())
          metrics.updatedBlocks = Some(lastUpdatedBlocks ++ updatedBlocks.toSeq)
          new InterruptibleIterator(context, cachedValues)

        } finally {
    
    
          loading.synchronized {
    
    
            loading.remove(key)
            loading.notifyAll()
          }
        }
    }
=> blockManager.get(key) 调用BlockManager的get方法获取数据
  /**
   * 通过blockmanager获取数据的入口
   * 优先从本地获取,如果本地没有就从远程获取
   */
  def get(blockId: BlockId): Option[BlockResult] = {
    
    
    val local = getLocal(blockId)
    if (local.isDefined) {
    
    
      logInfo(s"Found block $blockId locally")
      return local
    }
    val remote = getRemote(blockId)
    if (remote.isDefined) {
    
    
      logInfo(s"Found block $blockId remotely")
      return remote
    }
    None
  }

=> putInBlockManager
  private def putInBlockManager[T](
      key: BlockId,
      values: Iterator[T],
      level: StorageLevel,
      updatedBlocks: ArrayBuffer[(BlockId, BlockStatus)],
      effectiveStorageLevel: Option[StorageLevel] = None): Iterator[T] = {
    
    

    val putLevel = effectiveStorageLevel.getOrElse(level)
    // 持久化级别不是内存 仅仅是纯磁盘级别
    if (!putLevel.useMemory) {
    
    
      /*
       * This RDD is not to be cached in memory, so we can just pass the computed values as an
       * iterator directly to the BlockManager rather than first fully unrolling it in memory.
       */
      updatedBlocks ++=
        // TODO 如果持久化级别不是内存,直接调用BlockManager的putIterator方法将数据写入磁盘即可
        blockManager.putIterator(key, values, level, tellMaster = true, effectiveStorageLevel)
      blockManager.get(key) match {
    
    
        case Some(v) => v.data.asInstanceOf[Iterator[T]]
        case None =>
          logInfo(s"Failure to store $key")
          throw new BlockException(key, s"Block manager failed to return cached value for $key!")
      }
      // TODO 内存级别
    } else {
    
    
      /*
       * This RDD is to be cached in memory. In this case we cannot pass the computed values
       * to the BlockManager as an iterator and expect to read it back later. This is because
       * we may end up dropping a partition from memory store before getting it back.
       *
       * In addition, we must be careful to not unroll the entire partition in memory at once.
       * Otherwise, we may cause an OOM exception if the JVM does not have enough space for this
       * single partition. Instead, we unroll the values cautiously, potentially aborting and
       * dropping the partition to disk if applicable.
       */

      /**
       * TODO unrollSafely
       * 尝试将数据写入内存,无法写内存时写磁盘
       * 如果unrollSafely判断数据可以写入内存,就将数据写入内存
       * 如果unrollSafely判断数据无法可以写入内存,只能写入磁盘文件
       */
      blockManager.memoryStore.unrollSafely(key, values, updatedBlocks) match {
    
    
        case Left(arr) =>
          // We have successfully unrolled the entire partition, so cache it in memory
          updatedBlocks ++=
            // 写入内存
            blockManager.putArray(key, arr, level, tellMaster = true, effectiveStorageLevel)
          arr.iterator.asInstanceOf[Iterator[T]]
        case Right(it) =>
          // There is not enough space to cache this partition in memory
          val returnValues = it.asInstanceOf[Iterator[T]]
          // 如果数据无法写入内存,如果有磁盘级别就将数据写入磁盘
          if (putLevel.useDisk) {
    
    
            logWarning(s"Persisting partition $key to disk instead.")
            val diskOnlyLevel = StorageLevel(useDisk = true, useMemory = false,
              useOffHeap = false, deserialized = false, putLevel.replication)
            putInBlockManager[T](key, returnValues, level, updatedBlocks, Some(diskOnlyLevel))
          } else {
    
    
            returnValues
          }
      }
    }
  }
==> blockManager.memoryStore.unrollSafely
  def unrollSafely(
      blockId: BlockId,
      values: Iterator[Any],
      droppedBlocks: ArrayBuffer[(BlockId, BlockStatus)])
    : Either[Array[Any], Iterator[Any]] = {
    
    

    // Number of elements unrolled so far
    var elementsUnrolled = 0
    // Whether there is still enough memory for us to continue unrolling this block
    var keepUnrolling = true
    // Initial per-thread memory to request for unrolling blocks (bytes). Exposed for testing.
    val initialMemoryThreshold = unrollMemoryThreshold
    // How often to check whether we need to request more memory
    val memoryCheckPeriod = 16
    // Memory currently reserved by this thread for this particular unrolling operation
    var memoryThreshold = initialMemoryThreshold
    // Memory to request as a multiple of current vector size
    val memoryGrowthFactor = 1.5
    // Previous unroll memory held by this thread, for releasing later (only at the very end)
    val previousMemoryReserved = currentUnrollMemoryForThisThread
    // Underlying vector for unrolling the block
    var vector = new SizeTrackingVector[Any]

    // Request enough memory to begin unrolling
    keepUnrolling = reserveUnrollMemoryForThisThread(initialMemoryThreshold)

    if (!keepUnrolling) {
    
    
      logWarning(s"Failed to reserve initial memory threshold of " +
        s"${Utils.bytesToString(initialMemoryThreshold)} for computing block $blockId in memory.")
    }

    // Unroll this block safely, checking whether we have exceeded our threshold periodically
    try {
    
    
      while (values.hasNext && keepUnrolling) {
    
    
        vector += values.next()
        if (elementsUnrolled % memoryCheckPeriod == 0) {
    
    
          // If our vector's size has exceeded the threshold, request more memory
          val currentSize = vector.estimateSize()
          if (currentSize >= memoryThreshold) {
    
    
            val amountToRequest = (currentSize * memoryGrowthFactor - memoryThreshold).toLong
            // Hold the accounting lock, in case another thread concurrently puts a block that
            // takes up the unrolling space we just ensured here
            accountingLock.synchronized {
    
    
              if (!reserveUnrollMemoryForThisThread(amountToRequest)) {
    
    
                // If the first request is not granted, try again after ensuring free space
                // If there is still not enough space, give up and drop the partition
                val spaceToEnsure = maxUnrollMemory - currentUnrollMemory
                /**
                 * 反复循环,只要还有数据没有写入内存,并且可以继续尝试往内存写
                 * 就判断如果内存大小不够存放数据就调用ensureFreeSpace尝试清空一些内存空间
                 */
                if (spaceToEnsure > 0) {
    
    
                  val result = ensureFreeSpace(blockId, spaceToEnsure)
                  droppedBlocks ++= result.droppedBlocks
                }
                keepUnrolling = reserveUnrollMemoryForThisThread(amountToRequest)
              }
            }
            // New threshold is currentSize * memoryGrowthFactor
            memoryThreshold += amountToRequest
          }
        }
        elementsUnrolled += 1
      }

      if (keepUnrolling) {
    
    
        // We successfully unrolled the entirety of this block
        Left(vector.toArray)
      } else {
    
    
        // We ran out of space while unrolling the values for this block
        logUnrollFailureMessage(blockId, vector.estimateSize())
        Right(vector.iterator ++ values)
      }

    } finally {
    
    
      // If we return an array, the values returned do not depend on the underlying vector and
      // we can immediately free up space for other threads. Otherwise, if we return an iterator,
      // we release the memory claimed by this thread later on when the task finishes.
      if (keepUnrolling) {
    
    
        val amountToRelease = currentUnrollMemoryForThisThread - previousMemoryReserved
        releaseUnrollMemoryForThisThread(amountToRelease)
      }
    }
  }



Supongo que te gusta

Origin blog.csdn.net/m0_46449152/article/details/109561395
Recomendado
Clasificación