BlockManager的作用
Spark作为分布式的处理引擎,其数据也是分布式存储。在这种情况下,分布在不同节点的数据如何管理、如何知道数据的总体分布情况、如何进行数据操作等,这都就需要一个统一抽象来管理及完成这些工作。BlockManager顾名思义,用于块管理,分布式数据均已block为单位,BlockManager直接管控着这些块的信息获取、读、写等操作。
BlockManagerMaster
BlockManager离不开BlockManagerMaster,BlockManagerMaster就像是一个中央情报局,手握全局资料,拥有各种指挥权。各地的BlockManager都要向BlockManagerMaster上报自身的块信息,并接收和执行来自BlockManagerMaster的命令。
BlockManager和BlockManagerMaster的创建
均在SparkEnv创建的时候,源码对应于SparkEnv.scala的create代码
val blockManagerMaster = new BlockManagerMaster(registerOrLookupEndpoint(
BlockManagerMaster.DRIVER_ENDPOINT_NAME,
new BlockManagerMasterEndpoint(rpcEnv, isLocal, conf, listenerBus)),
conf, isDriver)
// 只有BlockManager的initialize方法调用之后BlockManager才可用,也就是说用之前必须初始化
// 因为初始化传入的应用ID,在BlockManager实例化的时候还不可知
val blockManager = new BlockManager(executorId, rpcEnv, blockManagerMaster,
serializerManager, conf, memoryManager, mapOutputTracker, shuffleManager,
blockTransferService, securityManager, numUsableCores)
具体driver端SparkEnv的创建,对应于SparkContext构建时的如下初始化代码
_env = createSparkEnv(_conf, isLocal, listenerBus)
具体executor端SparkEnv的创建,对应于CoarseGrainedExecutorBackend进程启动后的如下代码
val env = SparkEnv.createExecutorEnv(
driverConf, executorId, hostname, cores, cfg.ioEncryptionKey, isLocal = false)
从create可以看到,不管是在driver端还是executor端,BlockManagerMaster和BlockManager都会先后执行new BlockManagerMaster和new BlockManager代码,很明显BlockManager在driver和executor的创建方式没有明显不同。这里边的区别在于,在executor端执行new BlockManagerMaster实例化后其对应拿到的是driver端的BlockManagerMasterEndpoint。
BlockManager的初始化
BlockManager存在于driver端和executor端,初始化BlockManager的地方有两个,如下
- 在SparkContext初始化过程中,创建好Spark执行环境SparkEnv以后在该执行环境中初始化BlockManager
_env.blockManager.initialize(_applicationId)
- 另一个是在Executor实例的创建中初始化
if (!isLocal) {
env.blockManager.initialize(conf.getAppId)
...
}
CoarseGrainedExecutorBackend进程启动后会对应创建Executor。
交互
Executor上的BlockManager要和BlockManagerMaster通信,上报块状态信息。具体实现方式是通过Rpc来进行通信。
BlockManagerMasterEndpoint
在创建BlockManagerMaster的时候,同时实例化Driver端的BlockManagerMasterEndpoint,并注册到RpcEnv,用于Driver和Executor的BlockManager进行通信。
BlockManagerSlaveEndpoint
在BlockManager创建的时候,BlockManagerSlaveEndpoint也一起创建,BlockManagerSlaveEndpoint注册到RpcEnv。
关联:
在调用BlockManager的initialize的方法中,会将该BlockManager注册给BlockManagerMaster。
val id =
BlockManagerId(executorId, blockTransferService.hostName, blockTransferService.port, None)
val idFromMaster = master.registerBlockManager(
id,
maxOnHeapMemory,
maxOffHeapMemory,
slaveEndpoint)
那Executor上的BlockManager是怎么把自己注册给Master的?
RpcEndpointRef
即BlockManagerMasterEndpoint的引用,可以知道这一过程是通过Rpc操作来完成的。
def registerBlockManager(
blockManagerId: BlockManagerId,
maxOnHeapMemSize: Long,
maxOffHeapMemSize: Long,
slaveEndpoint: RpcEndpointRef): BlockManagerId = {
logInfo(s"Registering BlockManager $blockManagerId")
// BlockManager通过driverEndpoint即BlockManagerMasterEndpoint将自己注册给BlockManagerMaster
val updatedId = driverEndpoint.askSync[BlockManagerId](
RegisterBlockManager(blockManagerId, maxOnHeapMemSize, maxOffHeapMemSize, slaveEndpoint))
logInfo(s"Registered BlockManager $updatedId")
updatedId
}
BlockManager注册内容
每个Executor中的BlockManager向BlockManagerMaster注册成为ID及BlockManagerInfo的一种键值对形式的组合关系。
// BlockManagerId到BlockManagerInfo的映射.
private val blockManagerInfo = new mutable.HashMap[BlockManagerId, BlockManagerInfo]
BlockManager与BlockManagerMaster实际交互
通过以下方法的实现来完成
def receiveAndReply(context: RpcCallContext): PartialFunction[Any, Unit] = {
case _ => context.sendFailure(new SparkException(self + " won't reply anything"))
}
BlockManager与BlockManagerMaster发送对应的message到前边注册到RpcEnv的RpcEndpoint接口实现,master与slave中各自的receiveAndReply负责处理具体的命令和请求。包括更新块信息、删除RDD等。在Rpc的实现中可以看到,消息被发往一个中转站,然后逐一匹配处理。
Block存储
实际的块存储相关由以下两个实例来完成。分别对应着块在内存的存储和块在磁盘的存储。
MemoryStore:内存存储
DiskStore:磁盘存储
他们随着BlockManager的创建一起创建
private[spark] val memoryStore =
new MemoryStore(conf, blockInfoManager, serializerManager, memoryManager, this)
private[spark] val diskStore = new DiskStore(conf, diskBlockManager, securityManager)
memoryManager.setMemoryStore(memoryStore)
对应完成内存、磁盘数据的存、取等动作。