塊管理器BlockManager

簡介

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註冊的信息。

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中。

發佈了53 篇原創文章 · 獲贊 8 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章