Spark原始碼分析之cahce原理分析

happy19870612發表於2017-11-11

Task執行的時候是要去獲取Parent 的RDD對應的Partition的資料的,即它會呼叫RDD的iterator方法把對應的Partition的資料集給遍歷出來,然後寫入儲存,這個儲存可能是磁碟或者記憶體,取決於StorageLevel是什麼。

 

如果當前RDD的StorageLevel不為空,則表示已經存持久化了,我們可以直接在記憶體中獲取,而不是去計算Parent RDD。如果沒有StorageLevel,則表示沒有快取過,記憶體中沒有,則我們需要執行的資料就需要從Parent RDD計算出來。注意,這裡所謂的快取並不是使用什麼cache 元件,而直接是從本地讀取,本地沒有則從遠端,獲取的結果直接放入記憶體儲存,方便後續讀取,這才是真正cache的地方。

 

 

一 RDD的iterator方法

final defiterator(split:Partition, context: TaskContext): Iterator[T] = {
  // 如果StorageLevel不為空,表示該RDD已經持久化過了,可能是在記憶體,也有可能是在磁碟,
  //
如果是磁碟獲取的,需要把block快取在記憶體中
 
if (storageLevel!= StorageLevel.NONE) {
    getOrCompute(split,context)
  } else {
    // 進行rdd partition的計算或者根據checkpoint讀取資料
   
computeOrReadCheckpoint(split,context)
  }
}

二 RDD的getOrCompute

從記憶體或者磁碟獲取,如果磁碟獲取需要將block快取到記憶體

private[spark] def getOrCompute(partition: Partition, context: TaskContext): Iterator[T] = {
  // 根據rdd id建立RDDBlockId
  val blockId = RDDBlockId(id, partition.index)
  // 是否從快取的block讀取
  var readCachedBlock = true
  SparkEnv.get.blockManager.getOrElseUpdate(blockId, storageLevel, elementClassTag, () => {
    readCachedBlock = false // 如果呼叫了這個函式,說明沒有獲取到block,自然不能從cache中讀取
    // 需要呼叫該函式重新計算或者從checkpoint讀取
    computeOrReadCheckpoint(partition, context)
  }) match {
    // 獲取到了結果直接返回
    case Left(blockResult) =>
      // 如果從cache讀取block
      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]])
  }
}

 

三 BlockManager的getOrElseUpdate方法

如果指定的block存在,則直接獲取,否則呼叫makeIterator方法去計算block,然後持久化最後返回值

def getOrElseUpdate[T](
    blockId: BlockId,
    level: StorageLevel,
    classTag: ClassTag[T],
    makeIterator: () => Iterator[T]): Either[BlockResult, Iterator[T]] = {
  // 嘗試從本地獲取資料,如果獲取不到則從遠端獲取
  get[T](blockId)(classTag) match {
    case Some(block) =>
      return Left(block)
    case _ =>
      // Need to compute the block.
  }
  // 如果本地化和遠端都沒有獲取到資料,則呼叫makeIterator計算,最後將結果寫入block
  doPutIterator(blockId, makeIterator, level, classTag, keepReadLock = true) match {
    // 表示寫入成功
    case None =>
      // 從本地獲取資料塊
      val blockResult = getLocalValues(blockId).getOrElse {
        releaseLock(blockId)
        throw new SparkException(s"get() failed for block $blockId even though we held a lock")
      }
      releaseLock(blockId)
      Left(blockResult)
    // 如果寫入失敗
    case Some(iter) =>
      // 如果put操作失敗,表示可能是因為資料太大,無法寫入記憶體,又無法被磁碟drop,因此我們需要返回這個iterator
      // 呼叫者以至於他們能夠做出決定這個值是什麼,怎麼辦
      Right(iter)
  }
}

 

四 BlockManager的get方法

先從本地獲取資料,如果沒有則從遠端獲取

def get[T: ClassTag](blockId: BlockId): Option[BlockResult] = {
  // 從本地獲取block
  val local = getLocalValues(blockId)
  // 如果本地獲取到了則返回
  if (local.isDefined) {
    logInfo(s"Found block $blockId locally")
    return local
  }
  // 如果本地沒有獲取到則從遠端獲取
  val remote = getRemoteValues[T](blockId)
  // 如果遠端獲取到了則返回,沒有返回None
  if (remote.isDefined) {
    logInfo(s"Found block $blockId remotely")
    return remote
  }
  None
}

五 BlockManager的getLocalValues方法

從本地獲取block,如果存在返回BlockResult,不存在返回None;如果storage level是磁碟,則還需將得到的block快取到記憶體儲存,方便下次讀取

def getLocalValues(blockId: BlockId): Option[BlockResult] = {
  logDebug(s"Getting local block $blockId")
  // 呼叫block info manager,鎖定該block,然後讀取block,返回該block 後設資料block info
  blockInfoManager.lockForReading(blockId) match {
    // 沒有讀取到則返回None
    case None =>
      logDebug(s"Block $blockId was not found")
      None
    // 讀取到block後設資料
    case Some(info) =>
      // 獲取儲存級別storage level
      val level = info.level
      logDebug(s"Level for block $blockId is $level")
      // 如果使用記憶體,且記憶體memory store包含這個block id
      if (level.useMemory && memoryStore.contains(blockId)) {
        // 判斷是不是storage level是不是反序列化的,如果死反序列化的,則呼叫MemoryStoregetValues方法
        // 否則呼叫MemoryStoregetBytes然後反序列輸入流返回資料作為迭代器
        val iter: Iterator[Any] = if (level.deserialized) {
          memoryStore.getValues(blockId).get
        } else {
          serializerManager.dataDeserializeStream(
            blockId, memoryStore.getBytes(blockId).get.toInputStream())(info.classTag)
        }
        val ci = CompletionIterator[Any, Iterator[Any]](iter, releaseLock(blockId))
        // 構建一個BlockResult物件返回,這個物件包括資料,讀取方式以及位元組大小
        Some(new BlockResult(ci, DataReadMethod.Memory, info.size))
      }
      // 如果使用磁碟儲存,且disk store包含這個block則從磁碟獲取,並且把結果放入記憶體
      else if (level.useDisk && diskStore.contains(blockId)) {
        // 呼叫DiskStoregetBytes方法,如果需要反序列化,則進行反序列
        val iterToReturn: Iterator[Any] = {
          val diskBytes = diskStore.getBytes(blockId)
          if (level.deserialized) {
            val diskValues = serializerManager.dataDeserializeStream(
              blockId,
              diskBytes.toInputStream(dispose = true))(info.classTag)
            // 嘗試將從磁碟讀的溢寫的值載入到記憶體,方便後續快速讀取
            maybeCacheDiskValuesInMemory(info, blockId, level, diskValues)
          } else {
            // 如果不需要反序列化,首先將讀取到的流載入到記憶體,方便後續快速讀取
            val stream = maybeCacheDiskBytesInMemory(info, blockId, level, diskBytes)
              .map {_.toInputStream(dispose = false)}
              .getOrElse { diskBytes.toInputStream(dispose = true) }
            // 然後再返回反序列化之後的資料
            serializerManager.dataDeserializeStream(blockId, stream)(info.classTag)
          }
        }
        // 構建BlockResult返回
        val ci = CompletionIterator[Any, Iterator[Any]](iterToReturn, releaseLock(blockId))
        Some(new BlockResult(ci, DataReadMethod.Disk, info.size))
      } else {// 處理本地讀取block失敗,報告driver這是一個無效的block,將會刪除這個block
        handleLocalReadFailure(blockId)
      }
  }
}

 

五 BlockManager的getRemoteValues方法

從block所存放的其他block manager(其他節點)獲取block

private def getRemoteValues[T: ClassTag](blockId: BlockId): Option[BlockResult] = {
  val ct = implicitly[ClassTag[T]]
  // 將遠端fetch的結果進行反序列化,然後構建BlockResult返回
  getRemoteBytes(blockId).map { data =>
    val values =
      serializerManager.dataDeserializeStream(blockId, data.toInputStream(dispose = true))(ct)
    new BlockResult(values, DataReadMethod.Network, data.size)
  }
}

 

def getRemoteBytes(blockId: BlockId): Option[ChunkedByteBuffer] = {
  logDebug(s"Getting remote block $blockId")
  require(blockId != null, "BlockId is null")
  var runningFailureCount = 0
  var totalFailureCount = 0

  // 首先根據blockId獲取當前block存在在哪些block manager
  val locations = getLocations(blockId)
  // 最大允許的獲取block的失敗次數為該block對應的block manager數量
  val maxFetchFailures = locations.size
  var locationIterator = locations.iterator
  // 開始遍歷block manager
  while (locationIterator.hasNext) {
    val loc = locationIterator.next()
    logDebug(s"Getting remote block $blockId from $loc")
    // 通過呼叫BlockTransferSerivcefetchBlockSync方法從遠端獲取block
    val data = try {
      blockTransferService.fetchBlockSync(
        loc.host, loc.port, loc.executorId, blockId.toString).nioByteBuffer()
    } catch {
      case NonFatal(e) =>
        runningFailureCount += 1
        totalFailureCount += 1
        // 如果總的失敗數量大於了閥值則返回None
        if (totalFailureCount >= maxFetchFailures) {
          logWarning(s"Failed to fetch block after $totalFailureCount fetch failures. " +
            s"Most recent failure cause:", e)
          return None
        }

        logWarning(s"Failed to fetch remote block $blockId " +
          s"from $loc (failed attempt $runningFailureCount)", e)

        if (runningFailureCount >= maxFailuresBeforeLocationRefresh) {
          locationIterator = getLocations(blockId).iterator
          logDebug(s"Refreshed locations from the driver " +
            s"after ${runningFailureCount} fetch failures.")
          runningFailureCount = 0
        }

        // This location failed, so we retry fetch from a different one by returning null here
        null
    }
    // 返回ChunkedByteBuffer
    if (data != null) {
      return Some(new ChunkedByteBuffer(data))
    }
    logDebug(s"The value of block $blockId is null")
  }
  logDebug(s"Block $blockId not found")
  None
}

 

 

六RDD的computeOrReadCheckpoint

如果block沒有被持久化,即storage level為None,我們就需要進行計算或者從Checkpoint讀取資料;如果已經checkpoint了,則呼叫ietrator去讀取block資料,否則呼叫Parent的RDD的compute方法

private[spark] def computeOrReadCheckpoint(split: Partition, context: TaskContext): Iterator[T] =
{
  // 當前rdd是否已經checkpoint和物化了,如果已經checkpoint,則呼叫第一個parent rdditerator方法獲取
  // 如果沒有則開始計算
  if (isCheckpointedAndMaterialized) {
    firstParent[T].iterator(split, context)
  } else {
    // 則呼叫rddcompute方法開始計算,返回一個Iterator物件
    compute(split, context)
  }
}

 

 

相關文章