Storage模块详解

来源:互联网 发布:菜刀连接php图片木马 编辑:程序博客网 时间:2024/05/19 22:27

8.1 模块整体架构

Storage模块采用Master/Slave架构,Master负责整个Application的Block元数据信息的管理和维护;Slave负责将Block的更新等状态上报到Master,Master和Slave之间通过AKKA消息传递机制通信,Master与Slave之间并没有心跳检测而是间接通过Driver与Executor之间的心跳检测而来

Master的元数据信息管理与维护主要保存在BlockManagerMasterActor的三个数据结构中:

1)private valblockManagerInfo = new mutable.HashMap[BlockManagerId,BlockManagerInfo],保存BlockManagerId->BlockManagerInfo的映射,BlockManagerInfo保存Slave节点的内存使用情况、Block的状态、BlockManagerSlaveActor的Reference

2)private valblockManagerIdByExecutor=new mutable.HashMap[String,BlockManagerId]保存ExecutorID->BlockManagerId的映射

3)private valblockLocations=new JHashMap[BlockId,mutable.HashSet[BlockManagerId]],保存Block在哪些BlockManager上的HashMap,通过查询可知blockLocations可以找到某个Block所在的物理位置(HashSet说明该Block可能存在多个副本)

 

Slave的BlockManager创建后,需要向BlockManagerMasterActor发送RegisterBlockManager进行注册,master.registerBlockManager(blockManagerId,maxMemory,slaveActor)//参数分别为BlockManager标识,节点最大使用内存数,BlockManagerSlaveActor,利用blockManagerIdByExecutor把Slave信息保存在Master端

 

Driver端的实现:

SparkContext->SparkEnv->BlockManager->BlockManagerMaster转发请求至->BlockManagerMasterActor完成元数据管理和维护

 

Executor端的实现:

BlockManager->BlockManagerMaster持有Driver端的BlockManagerMasterActor的Reference,以此来完成Slave到Master的交互;除此之外BlockManager还拥有BlockManagerSlaveActor即该Actor的Reference会被上传到Master,Master会利用此Reference向Slave发送一些命令,比如删除Slave上的RDD、Shuffle相关的数据或者广播变量

 

8.1.2 源码组织结构

BlockManager提供读写Block的接口,BlockID格式是rdd_rddId_partitionId,其中Master端的BlockManager负责整个Application的Block管理工作;运行在Executor端的BlockManager负责管理该Executor上的Block,并向Driver的BlockManager汇报Block信息和接收它的命令

DiskBlockManager:管理及维护逻辑Block->物理Block的映射,根据BlockId->物理文件->hash到spark.local.dir设置的目录中

BlockStore有三种实现分别为DiskStore(Block存入Disk,写Disk是由DiskBlockObjectWriter),MemoryStore(Block存入Memory),TachyonStore(Block存入Tachyon)

TachyonBlockManager:逻辑Block->Tachyon文件系统的映射

 

8.1.3 Master和Slave的消息传递详解

1)Master->Slave的消息详解(以删除RDD为例的调用栈)

Ø  SparkContext.unpersist(id,blocking)

Ø  BlockManagerMaster.removeRdd(rddId,blocking)

Ø  BlockManagerMaster.askDriverWithReply[Future[Seq[Int]]](RemoveRdd(rddId))

Ø  AkkaUtils.askWithReply(message,driverActor,AKKA_RETRY_ATTEMPTS,AKKA_RETRY_INTERVAL_MS,timeout)

Ø  BlockManagerMasterActor.receiveWithLogging

case RemoveRdd(rddId)=>sender!removeRdd(rddId)

Ø  BlockManagerMasterActor.removeRdd(rddId)首先删除RDD的元数据信息,然后删除Slave的RDD信息

删除RDD的元数据信息

valblocks=blockLocations.keys.flatMap(_.asRDDId).filter(_.rddId==rddId)//根据rddId过滤得到待删除的blocks信息

Blocks.foreach{blockId=>valbms:mutable.HashSet[BlockManagerId]=blockLocations.get(blockId)}//将blockId信息映射为BlockManagerId

Bms.foreach(bm=>blockManagerInfo.get(bm).foreach(_.removeBlock(blockId)))//BlockManagerId->BlockManagerInfo,从blockManagerInfo中删除blockId对应的元数据信息

blockLocations.remove(blockId)//从blockLocations中删除blockId对应的元数据

 

其次删除Slave上的RDD信息

Ø  Master端向Slave发送删除Block命令

val removeMsg=RemoveRdd(rddId)

Future.sequence(

blockManagerInfo.values.map{bm=>

bm.slaveActor.ask(removeMsg)(akkaTimeout).mapTo[Int]  //遍历blockManagerInfo,每个blockManagerInfo.slaveActor.ask(removeMsg)即向其slaveActor发送removeMsg命令

}.toSeq

 

Ø  Slave接收到RemoveRdd的消息,调用BlockManager删除RDD

Case RemoveRdd(rddId)=>

doAsync[Int](“removing RDD”+rddId,sender){

blockManager.removeRdd(rddId)//BlockManager根据rddId删除RDD数据

}

 

BlockManager端的实现:

def removeRdd(rddId:Int):Int={

valblocksToRemove=blockInfo.keys.flatMap(_.asRDDId).filter(_.rddId=rddId)

blocksToRemove.foreach{

blockId=>removeBlock(blockId,tellMaster=false)//删除BlockId对应的Block

}

blocksToRemove.size

}

2)Slave->Master的消息详解

Slave向Master汇报某个Block的状态更新BlockManager.reportBlockStatus()的调用栈信息:

Ø  BlockManager.tryToReportBlockStatus

valneedRegister=!tryToReportBlockStatus(blockId,info,status,droppedMemorySize)

Ø  BlockManagerMaster.updateBlockInfo

master.updateBlockInfo(blockManagerId,blockId,storageLevel,inMemSize,onDiskSize,inTachyonSize)

Ø  BlockManagerMaster.askDriverWithReply

valres=askDriverWithReply[Boolean](UpdateBlockInfo(blockManagerId,blockId,storageLevel,memSize,diskSize,tachyonSize))

Ø  AkkaUtils.askWithReply(message,driverActor,AKKA_RETRY_ATTEMPTS,AKKA_RETRY_INTERVAL_MS,timeout)

Ø  BlockManagerMasterActor.receiveWithLogging()

case UpdateBlockInfo(

blockManagerId,blockId,storageLevel,deserializedSize,size,tachyonSize)=>

sender!updateBlockInfo(

blockManagerId,blockId,storageLevel,deserializedSize,size,tachyonSize

)

Ø  BlockManagerMasterActor.updateBlockInfo

blockManagerInfo(blockManagerId).updateBlockInfo(

blockId,storageLevel,memSize,diskSize,tachyonSize

)

Ø  locations=blockLocations.get(blockId) //根据blockId得到其物理位置信息

if(storageLevel.isValid){

locations.add(blockManagerId) //为该Block加入新的位置

}

else{
           locations.remove(blockManagerId)//删除无效的Block的位置

}

if(locations.size==0){

blockLocations.remove(blockId)

}   //如果locations的大小为0啦,说明Slave上再无此Block,直接根据blockId删除blockLocations记录

 

8.2 存储实现详解

8.2.1 存储级别

StorageLevel提供了多种存储策略包括MEMORY_ONLY,DISK_ONLY,MEMORY_AND_DISK等等

存储级别的选择:

1)默认的是MEMORY_ONLY即RDD的partition存储在内存中,多余的直接丢弃

2)减少内存使用则使用MEMORY_ONLY_SER

3)尽量不要落在硬盘,除非中间结果计算逻辑复杂

4)容错则选择MEMORY_ONLY_2

5)如果集群中有大量内存或者运行任务,则选择OFF_HEAP,多个Executor共享一个内存池,减少GC开销,缓存数据即使其Executor异常退出了,也不会丢失

 

8.2.2 模块类图

private abstract class BlockStore(valblockManager:BlockManager)extends Logging{
def putBytes(blockId,bytes,level):PutResult //根据StorageLevel将blockId标识的Block内容bytes写入系统

defputIterator(blockId,values,level,returnValues):PutResult //将values:Iterator[Any]写入系统

def putArray(blockId,values,level,returnValues):PutResult//将values:Array[Any]写入系统

 

8.2.3 DiskStore

DiskBlockManager管理文件,即逻辑Block->物理文件file->hash映射到spark.local.dir目录中

DiskBlockManager会为Executor在每个目录下创建一个子目录,子目录的命名方式是”spark-local-yyyyMMddHHmmss-xxxx”,在此目录下又可以生成至多spark.diskStore.subDirectories(默认值是64)的子目录,DiskBlockManager采用两段Hash定位到Block实际存放地址

8.2.4 MemoryStore

实际上通过HashMap管理Block数据

private val entries=newLinkedHashMap[BlockId,MemoryEntry](32m0.75f,true)

对外提供的写入接口通过MemoryStore.tryToPut,剩余内存容量检查(MemoryStore.ensureFreeSpace),内存超过最大内存则会立即返回调用者,如果存储级别还有Disk,则将当前数据通过DiskStore写入Disk,否则缓存不会被持久化

 

8.2.5 TachyonStore

Tachyon文件的读取和写入通过TachyonBlockManager完成,TachyonStore.getBytes(blockId:BlockId)与BlockStore类似,也会生成spark-tachyon-yyyyMMddHHmmss-xxxx目录,以及spark.tachyonStore.subDirectories(默认64)的子目录

 

8.2.6 Block存储的实现

CacheManager判断结果是否已缓存,如果是则直接读取缓存,否则开始计算

Ø  SparkEnv.get.CacheManager.getOrCompute(this,split,context,storageLevel)

Ø  val computedValues=rdd.computeOrReadCheckpoint(partition,context)//计算RDD或者读Checkpoint

Ø  blockManager.putArray(key,arr,level,telllMaster=true,effectiveStorageLevel)//计算的记过或写入缓存,返回值为PutResult,effectiveStorageLevel决定了结果最终写到哪,若tellMaster=true则需要reportBlockStatus

 

8.3 性能调优

8.3.1spark.local.dir

尽量在较快存储设备上配置更多的目录来增加其被使用的比例

8.3.2spark.executor.memory

Executor占用的内存

8.3.3 spark.storage.memoryFraction默认值是0.6,决定内存中有多少可以用于MemoryStore管理RDD Cache数据,多少内存用来满足任务运行时各种其他内存空间的需要

如果频繁发生GC,可以考虑降低此比例

8.3.4 Spark.streaming.blockInterval

设置Spark Streaming里Stream Receiver生成Block的时间间隔,默认是200ms
原创粉丝点击