块管理器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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章