文章目录
简介
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中。