文章目錄
簡介
Spark中的RDD-Cache、Shuffle-output以及broadcast的存儲實現都是基於BlockManager的,BlockManager提供了數據存儲(內存/文件存儲)接口。BlockManager是主從結構,在driver和所有executor節點上都會有BlockManager,每個節點上存儲的block信息都會彙報給driver端的blockManagerMaster作統一管理。BlockManager主要提供了讀取和寫數據的接口,可以從本地或者遠程讀取和寫數據,讀寫數據可以基於內存、磁盤或者是堆外空間 (off-Heap)。
-
如果我們對一個rdd進行了cache,cacheManager也是把數據放在了blockmanager中,後續task運行的時候可以直接從cacheManager中獲取到cacherdd,不用再從頭計算。
-
shuffle的過程利用blockmanager作爲數據的中轉站。
-
broadcast調度task到多個executor的時候,broadcast底層使用的數據存儲層。
-
Spark Streaming中一個ReceiverInputDStream接受到的數據也是先放在BlockManager中,然後封裝爲一個BlockRdd進行下一步運算的。
/** * 在每個節點(driver和executors)上運行的管理器, * 它提供將塊放入和檢索到本地和遠程存儲(memory、disk和off-heap)的接口。 * * 請注意,必須先調用[[initialize()]],然後才能使用BlockManager。 */ private[spark] class BlockManager( executorId: String, rpcEnv: RpcEnv, val master: BlockManagerMaster, val serializerManager: SerializerManager, val conf: SparkConf, memoryManager: MemoryManager, mapOutputTracker: MapOutputTracker, shuffleManager: ShuffleManager, val blockTransferService: BlockTransferService, securityManager: SecurityManager, numUsableCores: Int) extends BlockDataManager with BlockEvictionHandler with Logging {...}
這裏的Block和HDFS中談到的Block塊是有本質區別:HDFS中是對大文件進行分Block進行存儲,Block大小固定,如爲512M等;而Spark中的Block是用戶的操作單位,一個Block對應一塊有組織的內存,一個完整的文件或文件的區間端。
在RDD層面上我們瞭解到RDD是由不同的partition組成的,我們所進行的transformation和action是在partition上面進行的;而在storage模塊內部,RDD又被視爲由不同的block組成,對於RDD的存取是以block爲單位進行的,本質上partition和block是等價的,只是看待的角度不同。
BlockManager的創建
BlockManagerMaster和BlockManager都是在構造SparkEnv的時候創建的,Driver端是創建SparkContext的時候創建SparkEnv,Executor端的SparkEnv是在其守護進程CoarseGrainedExecutorBackend創建的時候創建的。
// SparkEnv.scala
val blockManagerPort = if (isDriver) {
conf.get(DRIVER_BLOCK_MANAGER_PORT)
} else {
conf.get(BLOCK_MANAGER_PORT)
}
val blockTransferService =
new NettyBlockTransferService(conf, securityManager, bindAddress, advertiseAddress,
blockManagerPort, numUsableCores)
val blockManagerMaster = new BlockManagerMaster(registerOrLookupEndpoint(
BlockManagerMaster.DRIVER_ENDPOINT_NAME,
new BlockManagerMasterEndpoint(rpcEnv, isLocal, conf, listenerBus)),
conf, isDriver)
// NB: blockManager is not valid until initialize() is called later.
val blockManager = new BlockManager(executorId, rpcEnv, blockManagerMaster,
serializerManager, conf, memoryManager, mapOutputTracker, shuffleManager,
blockTransferService, securityManager, numUsableCores)
構造blockManagerMaster的時候在Driver端是創建了一個BlockManagerMasterEndpoint並註冊到了rpcEnv中。而在executor端是獲取到了Driver端BlockManagerMasterEndpoint的引用BlockManagerMasterRef,隨後創建blockManager的時候創建了BlockManagerSlaveEndpoint。
BlockManager的註冊
blockManager創建後還不能直接使用,接着都會調用blockManager的initialize方法,通過與master通信向master進行註冊。master收到消息後會將blockManager的信息存到blockManagerInfo的map中,key爲blockManagerId(保存着executorId、host、post等信息),value爲BlockManagerInfo(保存着具體的block狀態信息及BlockManagerSlaveEndpoint的引用),註冊完後就可以真正使用了。BlockManagerMaster包含了集羣中整個BlockManager註冊的信息。
當需要從遠端獲取一個Block數據的時候,首先從driver上獲取到Block的真正存儲位置,然後調用blockTransferService的 fetchBlocks方法,去其他真正存儲數據的節點上fetch數據。
Master與Slave間的消息類型
BlockManagerMasterEndpoint接收的消息
// BlockManagerMasterEndpoint.scala
override def receiveAndReply(context: RpcCallContext): PartialFunction[Any, Unit] = {
// slave向master註冊,會保存在master的blockManagerInfo中
case RegisterBlockManager(blockManagerId, maxOnHeapMemSize, maxOffHeapMemSize, slaveEndpoint) =>
context.reply(register(blockManagerId, maxOnHeapMemSize, maxOffHeapMemSize, slaveEndpoint))
// Block的更新消息,更新master中blockManagerInfo信息
// 同時向driver中的listenerBus發送SparkListenerBlockUpdated消息
case _updateBlockInfo @
UpdateBlockInfo(blockManagerId, blockId, storageLevel, deserializedSize, size) =>
context.reply(updateBlockInfo(blockManagerId, blockId, storageLevel, deserializedSize, size))
listenerBus.post(SparkListenerBlockUpdated(BlockUpdatedInfo(_updateBlockInfo)))
// 用於獲取指定blockId的block所在的BlockManagerId列表
case GetLocations(blockId) =>
context.reply(getLocations(blockId))
// 獲取多個Block分別對應的BlockManagerId列表
case GetLocationsMultipleBlockIds(blockIds) =>
context.reply(getLocationsMultipleBlockIds(blockIds))
// 一個block有可能在多個節點上存在,返回一個節點列表
case GetPeers(blockManagerId) =>
context.reply(getPeers(blockManagerId))
// 根據BlockId,獲取所在executorEndpointRef,也就是BlockManagerSlaveEndpoint的引用
case GetExecutorEndpointRef(executorId) =>
context.reply(getExecutorEndpointRef(executorId))
// 獲取所有節點上的BlockManager的最大內存和剩餘內存
case GetMemoryStatus =>
context.reply(memoryStatus)
// 獲取所有節點上的BlockManager的最大磁盤空間和剩餘磁盤空間
case GetStorageStatus =>
context.reply(storageStatus)
// 獲取一個Block的狀態信息,位置,佔用內存和磁盤大小
case GetBlockStatus(blockId, askSlaves) =>
context.reply(blockStatus(blockId, askSlaves))
// 獲取所有符合過濾器的BlockId列表
case GetMatchingBlockIds(filter, askSlaves) =>
context.reply(getMatchingBlockIds(filter, askSlaves))
// 刪除rddId對應的Block數據
case RemoveRdd(rddId) =>
context.reply(removeRdd(rddId))
// 刪除shuffleId對應的BlockId的Block數據
case RemoveShuffle(shuffleId) =>
context.reply(removeShuffle(shuffleId))
// 刪除broadcastId對應的Block數據,removeFromDriver表示是否同時刪除driver中的數據
case RemoveBroadcast(broadcastId, removeFromDriver) =>
context.reply(removeBroadcast(broadcastId, removeFromDriver))
// 刪除一個blockId數據,會找到數據所在的slave,然後向slave發送一個刪除消息
case RemoveBlock(blockId) =>
removeBlockFromWorkers(blockId)
context.reply(true)
// 從BlockManagerInfo中刪除一個BlockManager, 並且刪除這個BlockManager上的所有的Blocks
case RemoveExecutor(execId) =>
removeExecutor(execId)
context.reply(true)
// 用於停止driver端的BlockManager
case StopBlockManagerMaster =>
context.reply(true)
stop()
// slave發送心跳給master, 證明自己還活着
case BlockManagerHeartbeat(blockManagerId) =>
context.reply(heartbeatReceived(blockManagerId))
// 用於檢查executor是否有緩存blocks(廣播變量的blocks不作考慮,因爲廣播變量的 block不會彙報給Master)
case HasCachedBlocks(executorId) =>
blockManagerIdByExecutor.get(executorId) match {
case Some(bm) =>
if (blockManagerInfo.contains(bm)) {
val bmInfo = blockManagerInfo(bm)
context.reply(bmInfo.cachedBlocks.nonEmpty)
} else {
context.reply(false)
}
case None => context.reply(false)
}
}
BlockManagerSlaveEndpoint接收的消息
// BlockManagerSlaveEndpoint.scala
// Operations that involve removing blocks may be slow and should be done asynchronously
override def receiveAndReply(context: RpcCallContext): PartialFunction[Any, Unit] = {
// slave刪除自己BlockManager上的一個Block
case RemoveBlock(blockId) =>
doAsync[Boolean]("removing block " + blockId, context) {
blockManager.removeBlock(blockId)
true
}
// 刪除rddId對應的Block數據
case RemoveRdd(rddId) =>
doAsync[Int]("removing RDD " + rddId, context) {
blockManager.removeRdd(rddId)
}
// 刪除shuffleId對應的BlockId的Block
case RemoveShuffle(shuffleId) =>
doAsync[Boolean]("removing shuffle " + shuffleId, context) {
if (mapOutputTracker != null) {
mapOutputTracker.unregisterShuffle(shuffleId)
}
SparkEnv.get.shuffleManager.unregisterShuffle(shuffleId)
}
// 刪除broadcastId對應的BlockId的Block
case RemoveBroadcast(broadcastId, _) =>
doAsync[Int]("removing broadcast " + broadcastId, context) {
blockManager.removeBroadcast(broadcastId, tellMaster = true)
}
// 獲取一個Block的存儲級別和所佔內存和磁盤大小
case GetBlockStatus(blockId, _) =>
context.reply(blockManager.getStatus(blockId))
// 獲取所有符合過濾器的BlockId列表
case GetMatchingBlockIds(filter, _) =>
context.reply(blockManager.getMatchingBlockIds(filter))
// 獲取所有線程的堆棧dump,在web UI上觸發的dump請求
case TriggerThreadDump =>
context.reply(Utils.getThreadDump())
// 複製blockId對應的block,可能由於executor失敗導致的blocks丟失
case ReplicateBlock(blockId, replicas, maxReplicas) =>
context.reply(blockManager.replicateBlock(blockId, replicas.toSet, maxReplicas))
}
BlockManager的存儲實現
在BlockManager被創建的時候創建了MemoryStore和DiskStore兩個對象用以存取block。如果內存中擁有足夠的內存,就使用MemoryStore存儲,如果不夠,就spill到磁盤中,通過DiskStore進行存儲(具體要看Block的存儲級別)。
DiskStore
DiskSore就是基於磁盤來存儲數據的,diskStore有一個成員DiskBlockManager,其主要作用就是邏輯block和磁盤block的映射,block的blockId對應磁盤文件中的一個文件。
MemoryStore
MemorySore是基於堆內存來存儲數據,其內部維護了一個hashmap來管理所有的block,以blockId爲key將block存放到hashmap中。