11.CacheManager源码分析

来源:互联网 发布:腾讯儿童智能台灯知乎 编辑:程序博客网 时间:2024/06/08 12:28

先来张原理图:


从rdd的iterator方法开始  , 因为在读取rdd的数据时是从iterator方法开始迭代数据的:
  1. /**
  2. * Internal method to this RDD; will read from cache if applicable, or otherwise compute it.
  3. * This should ''not'' be called by users directly, but is available for implementors of custom
  4. * subclasses of RDD.
  5. *
  6. * RDD的迭代方法 , 获取RDD中的数据
  7. */
  8. final def iterator(split: Partition, context: TaskContext): Iterator[T] = {
  9. // 如果StorageLevel不为NONE , 表示之前持久化过RDD那么就不直接去从父RDD执行算子计算新的RDD的partition
  10. // 优先尝试使用CacheManager去获取持久化的数据
  11. if (storageLevel != StorageLevel.NONE) {
  12. // CacheManager
  13. SparkEnv.get.cacheManager.getOrCompute(this, split, context, storageLevel)
  14. } else {
  15. computeOrReadCheckpoint(split, context)
  16. }
  17. }

1. 首先深入Cachemanager的getOrCompute方法 , 源码如下:
  1. def getOrCompute[T](
  2. rdd: RDD[T],
  3. partition: Partition,
  4. context: TaskContext,
  5. storageLevel: StorageLevel): Iterator[T] = {
  6. val key = RDDBlockId(rdd.id, partition.index)
  7. logDebug(s"Looking for partition $key")
  8. // 直接用BlockManager来获取数据 , 如果获取到了那就直接返回就好了
  9. blockManager.get(key) match {
  10. case Some(blockResult) =>
  11. // Partition is already materialized, so just return its values
  12. val inputMetrics = blockResult.inputMetrics
  13. val existingMetrics = context.taskMetrics
  14. .getInputMetricsForReadMethod(inputMetrics.readMethod)
  15. existingMetrics.incBytesRead(inputMetrics.bytesRead)
  16. val iter = blockResult.data.asInstanceOf[Iterator[T]]
  17. new InterruptibleIterator[T](context, iter) {
  18. override def next(): T = {
  19. existingMetrics.incRecordsRead(1)
  20. delegate.next()
  21. }
  22. }
  23. // 如果BlockManager没有获取到数据 , 虽然rdd持久化过但是因为未知的原因数据既不在本地内存或磁盘也不再远程的BlockManager上
  24. // 那么需要做后续的处理
  25. case None =>
  26. // 再次尝试一次BlockManager的get方法去获取数据 , 如果获取到了就直接返回数据若是没有获取到继续往后走
  27. val storedValues = acquireLockForPartition[T](key)
  28. if (storedValues.isDefined) {
  29. return new InterruptibleIterator[T](context, storedValues.get)
  30. }
  31. // Otherwise, we have to load the partition ourselves
  32. try {
  33. logInfo(s"Partition $key not found, computing it")
  34. // 如果computeOrReadCheckpoint()方法 , 如果rdd之前checkPoint过 , 那么就尝试读取它的checkpoint
  35. // 但是如果rdd没有checkpoint过 , 那么此时就别无选择 , 只能重新使用父rdd的数据执行算子计算一份
  36. val computedValues = rdd.computeOrReadCheckpoint(partition, context)
  37. // If the task is running locally, do not persist the result
  38. if (context.isRunningLocally) {
  39. return computedValues
  40. }
  41. // Otherwise, cache the values and keep track of any updates in block statuses
  42. val updatedBlocks = new ArrayBuffer[(BlockId, BlockStatus)]
  43. // 由于走CacheManager肯定意味着rdd是设置过持久化级别的
  44. // 只是因为某些原因持久化的数据没有找到那么才会走到这里来
  45. // 所以读取了checkpoint数据或者是重新计算数据之后要用putInBlockManager方法将数据在BlockManager中持久化一份
  46. val cachedValues = putInBlockManager(key, computedValues, storageLevel, updatedBlocks)
  47. val metrics = context.taskMetrics
  48. val lastUpdatedBlocks = metrics.updatedBlocks.getOrElse(Seq[(BlockId, BlockStatus)]())
  49. metrics.updatedBlocks = Some(lastUpdatedBlocks ++ updatedBlocks.toSeq)
  50. new InterruptibleIterator(context, cachedValues)
  51. } finally {
  52. loading.synchronized {
  53. loading.remove(key)
  54. loading.notifyAll()
  55. }
  56. }
  57. }
  58. }
上面的代码就是说一步一步往持久化级别更低的方式去获取数据 , 先内存在磁盘 , 最后若是走到了putInBlockManager方法那就表示这一份数据是经过父RDD重新计算得来,
那么本身这份数据是设置过持久化级别的但是就是在通过CacheManager获取数据时失败那么就需要再一次将这份数据持久化 , putInBlockManager方法源码如下:
  1. private def putInBlockManager[T](
  2. key: BlockId,
  3. values: Iterator[T],
  4. level: StorageLevel,
  5. updatedBlocks: ArrayBuffer[(BlockId, BlockStatus)],
  6. effectiveStorageLevel: Option[StorageLevel] = None): Iterator[T] = {
  7. val putLevel = effectiveStorageLevel.getOrElse(level)
  8. // 如果持久化级别没有指定内存级别仅仅是纯磁盘的级别
  9. if (!putLevel.useMemory) {
  10. /*
  11. * This RDD is not to be cached in memory, so we can just pass the computed values as an
  12. * iterator directly to the BlockManager rather than first fully unrolling it in memory.
  13. */
  14. updatedBlocks ++=
  15. // 那么直接调用BlockManager的putIterator()方法将数据写入磁盘即可
  16. blockManager.putIterator(key, values, level, tellMaster = true, effectiveStorageLevel)
  17. blockManager.get(key) match {
  18. case Some(v) => v.data.asInstanceOf[Iterator[T]]
  19. case None =>
  20. logInfo(s"Failure to store $key")
  21. throw new BlockException(key, s"Block manager failed to return cached value for $key!")
  22. }
  23. // 如果指定了内存存储级别
  24. } else {
  25. /*
  26. * This RDD is to be cached in memory. In this case we cannot pass the computed values
  27. * to the BlockManager as an iterator and expect to read it back later. This is because
  28. * we may end up dropping a partition from memory store before getting it back.
  29. *
  30. * In addition, we must be careful to not unroll the entire partition in memory at once.
  31. * Otherwise, we may cause an OOM exception if the JVM does not have enough space for this
  32. * single partition. Instead, we unroll the values cautiously, potentially aborting and
  33. * dropping the partition to disk if applicable.
  34. */
  35. // 这里会调用MemoryStore的unrollSafely()方法尝试将数据写入内存
  36. // 如果unrollSafely()方法判断数据可以写入内存那么写入 , 反之则只能写入文件
  37. blockManager.memoryStore.unrollSafely(key, values, updatedBlocks) match {
  38. case Left(arr) =>
  39. // We have successfully unrolled the entire partition, so cache it in memory
  40. updatedBlocks ++=
  41. blockManager.putArray(key, arr, level, tellMaster = true, effectiveStorageLevel)
  42. arr.iterator.asInstanceOf[Iterator[T]]
  43. case Right(it) =>
  44. // There is not enough space to cache this partition in memory
  45. val returnValues = it.asInstanceOf[Iterator[T]]
  46. // 如果有些数据是在无法写入内存那么就判断数据是否有磁盘级别 , 有的话就写入磁盘
  47. if (putLevel.useDisk) {
  48. logWarning(s"Persisting partition $key to disk instead.")
  49. val diskOnlyLevel = StorageLevel(useDisk = true, useMemory = false,
  50. useOffHeap = false, deserialized = false, putLevel.replication)
  51. putInBlockManager[T](key, returnValues, level, updatedBlocks, Some(diskOnlyLevel))
  52. } else {
  53. returnValues
  54. }
  55. }
  56. }
  57. }
在深入到unrollSafely()方法尝试将数据写入内存 :
  1. def unrollSafely(
  2. blockId: BlockId,
  3. values: Iterator[Any],
  4. droppedBlocks: ArrayBuffer[(BlockId, BlockStatus)])
  5. : Either[Array[Any], Iterator[Any]] = {
  6. // Number of elements unrolled so far
  7. var elementsUnrolled = 0
  8. // Whether there is still enough memory for us to continue unrolling this block
  9. var keepUnrolling = true
  10. // Initial per-thread memory to request for unrolling blocks (bytes). Exposed for testing.
  11. val initialMemoryThreshold = unrollMemoryThreshold
  12. // How often to check whether we need to request more memory
  13. val memoryCheckPeriod = 16
  14. // Memory currently reserved by this thread for this particular unrolling operation
  15. var memoryThreshold = initialMemoryThreshold
  16. // Memory to request as a multiple of current vector size
  17. val memoryGrowthFactor = 1.5
  18. // Previous unroll memory held by this thread, for releasing later (only at the very end)
  19. val previousMemoryReserved = currentUnrollMemoryForThisThread
  20. // Underlying vector for unrolling the block
  21. var vector = new SizeTrackingVector[Any]
  22. // Request enough memory to begin unrolling
  23. keepUnrolling = reserveUnrollMemoryForThisThread(initialMemoryThreshold)
  24. if (!keepUnrolling) {
  25. logWarning(s"Failed to reserve initial memory threshold of " +
  26. s"${Utils.bytesToString(initialMemoryThreshold)} for computing block $blockId in memory.")
  27. }
  28. // Unroll this block safely, checking whether we have exceeded our threshold periodically
  29. try {
  30. while (values.hasNext && keepUnrolling) {
  31. vector += values.next()
  32. if (elementsUnrolled % memoryCheckPeriod == 0) {
  33. // If our vector's size has exceeded the threshold, request more memory
  34. val currentSize = vector.estimateSize()
  35. if (currentSize >= memoryThreshold) {
  36. val amountToRequest = (currentSize * memoryGrowthFactor - memoryThreshold).toLong
  37. // Hold the accounting lock, in case another thread concurrently puts a block that
  38. // takes up the unrolling space we just ensured here
  39. accountingLock.synchronized {
  40. if (!reserveUnrollMemoryForThisThread(amountToRequest)) {
  41. // If the first request is not granted, try again after ensuring free space
  42. // If there is still not enough space, give up and drop the partition
  43. val spaceToEnsure = maxUnrollMemory - currentUnrollMemory
  44. // 反复判断只要还有数据需要写入内存并且可以继续尝试写入内存那么就判断内存大小是否够用
  45. // 如果不够用的话调用ensureFreeSpace()反复尝试清空一些内存空间
  46. if (spaceToEnsure > 0) {
  47. val result = ensureFreeSpace(blockId, spaceToEnsure)
  48. droppedBlocks ++= result.droppedBlocks
  49. }
  50. keepUnrolling = reserveUnrollMemoryForThisThread(amountToRequest)
  51. }
  52. }
  53. // New threshold is currentSize * memoryGrowthFactor
  54. memoryThreshold += amountToRequest
  55. }
  56. }
  57. elementsUnrolled += 1
  58. }
  59. if (keepUnrolling) {
  60. // We successfully unrolled the entirety of this block
  61. Left(vector.toArray)
  62. } else {
  63. // We ran out of space while unrolling the values for this block
  64. logUnrollFailureMessage(blockId, vector.estimateSize())
  65. Right(vector.iterator ++ values)
  66. }
  67. } finally {
  68. // If we return an array, the values returned do not depend on the underlying vector and
  69. // we can immediately free up space for other threads. Otherwise, if we return an iterator,
  70. // we release the memory claimed by this thread later on when the task finishes.
  71. if (keepUnrolling) {
  72. val amountToRelease = currentUnrollMemoryForThisThread - previousMemoryReserved
  73. releaseUnrollMemoryForThisThread(amountToRelease)
  74. }
  75. }
  76. }

最后在看看putIterator方法将数据写入磁盘:
  1. def putIterator(
  2. blockId: BlockId,
  3. values: Iterator[Any],
  4. level: StorageLevel,
  5. tellMaster: Boolean = true,
  6. effectiveStorageLevel: Option[StorageLevel] = None): Seq[(BlockId, BlockStatus)] = {
  7. require(values != null, "Values is null")
  8. doPut(blockId, IteratorValues(values), level, tellMaster, effectiveStorageLevel)
  9. }
其实还是调用了BlockManager的doPut方法 , doPut方法调用就是上一章节讲到的BlockManager原理咯 , 这里不在分析
以上就是CacheManager的整个过程 , 其实从我们的代码中设置rdd的持久化persist开始 , CacheManager就开始工作 , 将数据持久化 , 当后面在需要用到这个rdd的时候 , 调用rdd的iterator方法开始找寻持久化的rdd对应的那份数据 , 若是没有找到则从父RDD重新计算并再一次进行持久化 ,这就是CacheManager的整个作用 !



原创粉丝点击