Spark原始碼分析之cahce原理分析
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是不是反序列化的,如果死反序列化的,則呼叫MemoryStore的getValues方法
// 否則呼叫MemoryStore的getBytes然後反序列輸入流返回資料作為迭代器
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)) {
// 呼叫DiskStore的getBytes方法,如果需要反序列化,則進行反序列
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")
// 通過呼叫BlockTransferSerivce的fetchBlockSync方法從遠端獲取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 rdd的iterator方法獲取
// 如果沒有則開始計算
if (isCheckpointedAndMaterialized) {
firstParent[T].iterator(split, context)
} else {
// 則呼叫rdd的compute方法開始計算,返回一個Iterator物件
compute(split, context)
}
}
相關文章
- Spark原始碼分析之DiskBlockMangaer分析Spark原始碼BloC
- Spark原始碼分析之MemoryManagerSpark原始碼
- Spark原始碼分析之BlockStoreSpark原始碼BloC
- spark 原始碼分析之十三 -- SerializerManager剖析Spark原始碼
- Spark原始碼分析之Checkpoint機制Spark原始碼
- Spark 原始碼分析系列Spark原始碼
- spark 原始碼分析之十九 -- Stage的提交Spark原始碼
- spark 原始碼分析之十八 -- Spark儲存體系剖析Spark原始碼
- spark 原始碼分析之十五 -- Spark記憶體管理剖析Spark原始碼記憶體
- Spark原始碼分析之BlockManager通訊機制Spark原始碼BloC
- spark 原始碼分析之十六 -- Spark記憶體儲存剖析Spark原始碼記憶體
- Spark RPC框架原始碼分析(三)Spark心跳機制分析SparkRPC框架原始碼
- Spark job分配流程原始碼分析Spark原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- GCD原始碼原理分析GC原始碼
- Spark原始碼分析之Worker啟動通訊機制Spark原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- spark core原始碼分析3 Master HASpark原始碼AST
- Struts2 原始碼分析-----工作原理分析原始碼
- SpringMVC原始碼分析原理SpringMVC原始碼
- Guava 原始碼分析(Cache 原理)Guava原始碼
- spark 原始碼分析之十四 -- broadcast 是如何實現的?Spark原始碼AST
- Spring 原始碼分析之 bean 例項化原理Spring原始碼Bean
- JVM原始碼分析之javaagent原理完全解讀JVM原始碼Java
- redis原始碼分析(二)、redis原始碼分析之sds字串Redis原始碼字串
- 原始碼分析之 HashMap原始碼HashMap
- 原始碼分析之AbstractQueuedSynchronizer原始碼
- 原始碼分析之ArrayList原始碼
- spark 原始碼分析之十九 -- DAG的生成和Stage的劃分Spark原始碼
- Composer 工作原理 [原始碼分析]原始碼
- AQS的原理及原始碼分析AQS原始碼
- Spring原始碼分析:BeanPostProcessor原理Spring原始碼Bean
- PriorityQueue原理分析——基於原始碼原始碼
- Volcano 原理、原始碼分析(一)原始碼
- Spring原始碼分析之 lazy-init 實現原理Spring原始碼
- Spark RPC框架原始碼分析(一)簡述SparkRPC框架原始碼
- spark streaming原始碼分析1 StreamingContextSpark原始碼GCContext
- spark core原始碼分析2 master啟動流程Spark原始碼AST