note32sparkHA内存调优.docx
- 文档编号:27003010
- 上传时间:2023-06-25
- 格式:DOCX
- 页数:26
- 大小:2.37MB
note32sparkHA内存调优.docx
《note32sparkHA内存调优.docx》由会员分享,可在线阅读,更多相关《note32sparkHA内存调优.docx(26页珍藏版)》请在冰豆网上搜索。
note32sparkHA内存调优
Spark
一、HA原理:
Hdfs的HA角色任务:
1.ZKFC:
监控namenode的状态,改变状态。
2.Journalnode:
同步editslog,多台
3.Zookeeper:
选举
MasterHA:
filesystem(启动效率慢,需要手动)
Zookeeper(快,自动)
执行启动命令时,worker会将自己的信息注册到master中。
所以master的元数据信息就是workers、waitingDrivers、waitingApps,这些信息会存储到zookeeper中。
Master宕机时:
1.zookeeper会进行选举
2.选出的新master会去zookeeper中拉取元数据信息,并且把standby转为alive。
3.Master告知worker以后发送心跳给自己
Spark在主备切换是不能提交新的application,因为需要向master申请资源
主备切换时已运行的任务不受影响,如果这时任务完成结果会返回给driver
二、HA搭建
1.配置文件spark-env.sh添加以下内容
ExportSPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER-Dspark.deploy.zookeeper.url=hadoop1:
2181,hadoop2:
2181,hadoop3:
2181-Dspark.deploy.zookeeper.dir=/spark0731"
2.配置文件同步到其他节点,客户端不需要同步。
这个错因为其他节点指定的master是节点node1,可以将这行配置直接删掉或都将masterid指定自己。
启动时在哪个节点启动start-all,哪个就是master。
三、内存管理
Spark申请内存
1.Spark在代码中new一个对象实例
2.JVM从堆内内存分配空间,创建对象并返回对象引用
3.Spark保存该对象的引用,记录该对象占用的内存
释放内存
1.Spark记录该对象释放的内存,删除该对象的引用
2.等待JVM的垃圾回收机制释放该对象占用的堆内内存
序列化的对象,spark可以计算出内存使用大小。
非序列化的对象,spark是周期性采样计算,内存估算会有误差。
此外,被标记为删除的对象可能没有被jvm回收,也会有误差。
因此spark无法避免OOM。
Spark引入了对外内存
在默认情况下堆外内存并不启用,可通过配置spark.memory.offHeap.enabled参数启用,并由spark.memory.offHeap.size参数设定堆外空间的大小。
除了没有other空间,堆外内存与堆内内存的划分方式相同,所有运行中的并发任务共享存储内存和执行内存。
静态内存管理-堆内
可用的存储内存=systemMaxMemory*spark.storage.memoryFraction*spark.storage.safetyFraction
可用的执行内存=systemMaxMemory*spark.shuffle.memoryFraction*spark.shuffle.safetyFraction
堆外的空间分配较为简单,只有存储内存和执行内存,如图3所示。
可用的执行内存和存储内存占用的空间大小直接由参数spark.memory.storageFraction决定,由于堆外内存占用的空间可以被精确计算,所以无需再设定保险区域。
静态内存管理机制实现起来较为简单,但如果用户不熟悉Spark的存储机制,或没有根据具体的数据规模和计算任务或做相应的配置,很容易造成”一半海水,一半火焰”的局面,即存储内存和执行内存中的一方剩余大量的空间,而另一方却早早被占满,不得不淘汰或移出旧的内容以存储新的内容。
由于新的内存管理机制的出现,这种方式目前已经很少有开发者使用,出于兼容旧版本的应用程序的目的,Spark仍然保留了它的实现。
Spark1.6之后引入的统一内存管理机制,与静态内存管理的区别在于存储内存和执行内存共享同一块空间,可以动态占用对方的空闲区域,如图4和图5所示
其中最重要的优化在于动态占用机制,其规则如下:
设定基本的存储内存和执行内存区域(spark.storage.storageFraction参数),该设定确定了双方各自拥有的空间的范围
双方的空间都不足时,则存储到硬盘;若己方空间不足而对方空余时,可借用对方的空间;(存储空间不足是指不足以放下一个完整的Block)
执行内存的空间被对方占用后,可让对方将占用的部分转存到硬盘,然后”归还”借用的空间
存储内存的空间被对方占用后,无法让对方”归还”,因为需要考虑Shuffle过程中的很多因素,实现起来较为复杂[4]
凭借统一内存管理机制,Spark在一定程度上提高了堆内和堆外内存资源的利用率,降低了开发者维护Spark内存的难度,但并不意味着开发者可以高枕无忧。
譬如,所以如果存储内存的空间太大或者说缓存的数据过多,反而会导致频繁的全量垃圾回收,降低任务执行时的性能,因为缓存的RDD数据通常都是长期驻留内存的[5]。
所以要想充分发挥Spark的性能,需要开发者进一步了解存储内存和执行内存各自的管理方式和实现原理。
RDD缓存的过程
RDD在缓存到存储内存之前,Partition中的数据一般以迭代器(Iterator)的数据结构来访问,这是Scala语言中一种遍历数据集合的方法。
通过Iterator可以获取分区中每一条序列化或者非序列化的数据项(Record),这些Record的对象实例在逻辑上占用了JVM堆内内存的other部分的空间,同一Partition的不同Record的空间并不连续。
RDD在缓存到存储内存之后,Partition被转换成Block,Record在堆内或堆外存储内存中占用一块连续的空间。
将Partition由不连续的存储空间转换为连续存储空间的过程,Spark称之为”展开”(Unroll)。
Block有序列化和非序列化两种存储格式,具体以哪种方式取决于该RDD的存储级别。
非反序列化的Block以一种DeserializedMemoryEntry的数据结构定义,用一个数组存储所有的Java对象,非序列化的Block则以SerializedMemoryEntry的数据结构定义,用字节缓冲区(ByteBuffer)来存储二进制数据。
每个Executor的Storage模块用一个链式Map结构(LinkedHashMap)来管理堆内和堆外存储内存中所有的Block对象的实例[6],对这个LinkedHashMap新增和删除间接记录了内存的申请和释放。
因为不能保证存储空间可以一次容纳Iterator中的所有数据,当前的计算任务在Unroll时要向MemoryManager申请足够的Unroll空间来临时占位,空间不足则Unroll失败,空间足够时可以继续进行。
对于序列化的Partition,其所需的Unroll空间可以直接累加计算,一次申请。
而非序列化的Partition则要在遍历Record的过程中依次申请,即每读取一条Record,采样估算其所需的Unroll空间并进行申请,空间不足时可以中断,释放已占用的Unroll空间。
如果最终Unroll成功,当前Partition所占用的Unroll空间被转换为正常的缓存RDD的存储空间,如下图8所示。
图8.SparkUnroll示意图
在图3和图5中可以看到,在静态内存管理时,Spark在存储内存中专门划分了一块Unroll空间,其大小是固定的,统一内存管理时则没有对Unroll空间进行特别区分,当存储空间不足时会根据动态占用机制进行处理。
3.3淘汰和落盘
由于同一个Executor的所有的计算任务共享有限的存储内存空间,当有新的Block需要缓存但是剩余空间不足且无法动态占用时,就要对LinkedHashMap中的旧Block进行淘汰(Eviction),而被淘汰的Block如果其存储级别中同时包含存储到磁盘的要求,则要对其进行落盘(Drop),否则直接删除该Block。
存储内存的淘汰规则为:
被淘汰的旧Block要与新Block的MemoryMode相同,即同属于堆外或堆内内存
新旧Block不能属于同一个RDD,避免循环淘汰
旧Block所属RDD不能处于被读状态,避免引发一致性问题
遍历LinkedHashMap中Block,按照最近最少使用(LRU)的顺序淘汰,直到满足新Block所需的空间。
其中LRU是LinkedHashMap的特性。
落盘的流程则比较简单,如果其存储级别符合_useDisk为true的条件,再根据其_deserialized判断是否是非序列化的形式,若是则对其进行序列化,最后将数据存储到磁盘,在Storage模块中更新其信息。
4.执行内存管理
4.1多任务间内存分配
Executor内运行的任务同样共享执行内存,Spark用一个HashMap结构保存了任务到内存耗费的映射。
每个任务可占用的执行内存大小的范围为1/2N~1/N,其中N为当前Executor内正在运行的任务的个数。
每个任务在启动之时,要向MemoryManager请求申请最少为1/2N的执行内存,如果不能被满足要求则该任务被阻塞,直到有其他任务释放了足够的执行内存,该任务才可以被唤醒。
4.2Shuffle的内存占用
执行内存主要用来存储任务在执行Shuffle时占用的内存,Shuffle是按照一定规则对RDD数据重新分区的过程,我们来看Shuffle的Write和Read两阶段对执行内存的使用:
ShuffleWrite
若在map端选择普通的排序方式,会采用ExternalSorter进行外排,在内存中存储数据时主要占用堆内执行空间。
若在map端选择Tungsten的排序方式,则采用ShuffleExternalSorter直接对以序列化形式存储的数据排序,在内存中存储数据时可以占用堆外或堆内执行空间,取决于用户是否开启了堆外内存以及堆外执行内存是否足够。
ShuffleRead
在对reduce端的数据进行聚合时,要将数据交给Aggregator处理,在内存中存储数据时占用堆内执行空间。
如果需要进行最终结果排序,则要将再次将数据交给ExternalSorter处理,占用堆内执行空间。
在ExternalSorter和Aggregator中,Spark会使用一种叫AppendOnlyMap的哈希表在堆内执行内存中存储数据,但在Shuffle过程中所有数据并不能都保存到该哈希表中,当这个哈希表占用的内存会进行周期性地采样估算,当其大到一定程度,无法再从MemoryManager申请到新的执行内存时,Spark就会将其全部内容存储到磁盘文件中,这个过程被称为溢存(Spill),溢存到磁盘的文件最后会被归并(Merge)。
ShuffleWrite阶段中用到的Tungsten是Databricks公司提出的对Spark优化内存和CPU使用的计划[9],解决了一些JVM在性能上的限制和弊端。
Spark会根据Shuffle的情况来自动选择是否采用Tungsten排序。
Tungsten采用的页式内存管理机制建立在MemoryManager之上,即Tungsten对执行内存的使用进行了一步的抽象,这样在Shuffle过程中无需关心数据具体存储在堆内还是堆外。
每个内存页用一个MemoryBlock来定义,并用Objectobj和longoffset这两个变量统一标识一个内存页在系统内存中的地址。
堆内的MemoryBlock是以long型数组的形式分配的内存,其obj的值为是这个数组的对象引用,offset是long型数组的在JVM中的初始偏移地址,两者配合使用可以定位这个数组在堆内的绝对地址;堆外的MemoryBlock是直接申请到的内存块,其obj为null,offset是这个内存块在系统内存中的64位绝对地址。
Spark用MemoryBlock巧妙地将堆内和堆外内存页统一抽象封装,并用页表(pageTable)管理每个Task申请到的内存页。
Tungsten页式管理下的所有内存用64位的逻辑地址表示,由页号和页内偏移量组成:
页号:
占13位,唯一标识一个内存页,Spark在申请内存页之前要先申请空闲页号。
页内偏移量:
占51位,是在使用内存页存储数据时,数据在页内的偏移地址。
有了统一的寻址方式,Spark可以用64位逻辑地址的指针定位到堆内或堆外的内存,整个ShuffleWrite排序的过程只需要对指针进行排序,并且无需反序列化,整个过程非常高效,对于内存访问效率和CPU使用效率带来了明显的提升[10]。
Spark的存储内存和执行内存有着截然不同的管理方式:
对于存储内存来说,Spark用一个LinkedHashMap来集中管理所有的Block,Block由需要缓存的RDD的Partition转化而成;而对于执行内存,Spark用AppendOnlyMap来存储Shuffle过程中的数据,在Tungsten排序中甚至抽象成为页式内存管理,开辟了全新的JVM内存管理机制。
Shuffle
HashShuffleManager方式:
小文件个数=maptask*reducetask
经过优化,task的内存复用。
优化后的,小文件个数=cores*reducetask
Shufflefilegroup(磁盘小文件组)
一般工作中一个core分配2-3个task,一个task一般是1g
SortShuffleManager普通机制,采取rangepartitioner分区排序溢写。
生成一个大的磁盘文件,同时生成一个索引文件。
SortShuffleManagerBypass机制
这种机制中间没有排序,没有溢写,直接写。
有时候希望少一些文件而由不需要排序可以使用这种机制。
bypass运行机制的触发条件如下:
shufflereducetask数量小于spark.shuffle.sort.bypassMergeThreshold参数的值,默认200。
配置信息三种方式:
代码指定、配置文件指定、启动命令指定。
SparkConf.set("spark.shuffle.file.buffer","64k")
推荐使用方式
spark-submit--confspark.shuffle.file.buffer=64k--conf配置信息=配置值...
spark.shuffle.file.buffer
默认值:
32k
参数说明:
该参数用于设置shufflewritetask的BufferedOutputStream的buffer缓冲大小。
将数据写到磁盘文件之前,会先写入buffer缓冲中,待缓冲写满之后,才会溢写到磁盘。
调优建议:
如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如64k),从而减少shufflewrite过程中溢写磁盘文件的次数,也就可以减少磁盘IO次数,进而提升性能。
在实践中发现,合理调节该参数,性能会有1%~5%的提升。
spark.reducer.maxSizeInFlight
默认值:
48m
参数说明:
该参数用于设置shufflereadtask的buffer缓冲大小,而这个buffer缓冲决定了每次能够拉取多少数据。
调优建议:
如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如96m),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。
在实践中发现,合理调节该参数,性能会有1%~5%的提升。
spark.shuffle.io.maxRetries
默认值:
3
参数说明:
shufflereadtask从shufflewritetask所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的。
该参数就代表了可以重试的最大次数。
如果在指定次数之内拉取还是没有成功,就可能会导致作业执行失败。
调优建议:
对于那些包含了特别耗时的shuffle操作的作业,建议增加重试最大次数(比如60次),以避免由于JVM的fullgc或者网络不稳定等因素导致的数据拉取失败。
在实践中发现,对于针对超大数据量(数十亿~上百亿)的shuffle过程,调节该参数可以大幅度提升稳定性。
shufflefilenotfindtaskScheduler不负责重试task,由DAGScheduler负责重试stage
spark.shuffle.io.retryWait
默认值:
5s
参数说明:
具体解释同上,该参数代表了每次重试拉取数据的等待间隔,默认是5s。
调优建议:
建议加大间隔时长(比如60s),以增加shuffle操作的稳定性。
spark.shuffle.memoryFraction
默认值:
0.2
参数说明:
该参数代表了Executor内存中,分配给shufflereadtask进行聚合操作的内存比例,默认是20%。
调优建议:
在资源参数调优中讲解过这个参数。
如果内存充足,而且很少使用持久化操作,建议调高这个比例,给shuffleread的聚合操作更多内存,以避免由于内存不足导致聚合过程中频繁读写磁盘。
在实践中发现,合理调节该参数可以将性能提升10%左右。
spark.shuffle.manager
默认值:
sort
参数说明:
该参数用于设置ShuffleManager的类型。
Spark1.5以后,有三个可选项:
hash、sort和tungsten-sort。
HashShuffleManager是Spark1.2以前的默认选项,但是Spark1.2以及之后的版本默认都是SortShuffleManager了。
tungsten-sort与sort类似,但是使用了tungsten计划中的堆外内存管理机制,内存使用效率更高。
调优建议:
由于SortShuffleManager默认会对数据进行排序,因此如果你的业务逻辑中需要该排序机制的话,则使用默认的SortShuffleManager就可以;而如果你的业务逻辑不需要对数据进行排序,那么建议参考后面的几个参数调优,通过bypass机制或优化的HashShuffleManager来避免排序操作,同时提供较好的磁盘读写性能。
这里要注意的是,tungsten-sort要慎用,因为之前发现了一些相应的bug。
spark.shuffle.sort.bypassMergeThreshold
默认值:
200
参数说明:
当ShuffleManager为SortShuffleManager时,如果shufflereadtask的数量小于这个阈值(默认是200),则shufflewrite过程中不会进行排序操作,而是直接按照未经优化的HashShuffleManager的方式去写数据,但是最后会将每个task产生的所有临时磁盘文件都合并成一个文件,并会创建单独的索引文件。
调优建议:
当你使用SortShuffleManager时,如果的确不需要排序操作,那么建议将这个参数调大一些,大于shufflereadtask的数量。
那么此时就会自动启用bypass机制,map-side就不会进行排序了,减少了排序的性能开销。
但是这种方式下,依然会产生大量的磁盘文件,因此shufflewrite性能有待提高。
spark.shuffle.consolidateFiles
默认值:
false
参数说明:
如果使用HashShuffleManager,该参数有效。
如果设置为true,那么就会开启consolidate机制,会大幅度合并shufflewrite的输出文件,对于shufflereadtask数量特别多的情况下,这种方法可以极大地减少磁盘IO开销,提升性能。
调优建议:
如果的确不需要SortShuffleManager的排序机制,那么除了使用bypass机制,还可以尝试将spark.shffle.manager参数手动指定为hash,使用HashShuffleManager,同时开启consolidate机制。
在实践中尝试过,发现其性能比开启了bypass机制的SortShuffleManager要高出10%~30%。
问题:
redcueoom
问题原因:
解决办法:
1、减少拉取的数据量
2、***增加shuffle聚合内存比例
3、增加Executor的内存
Shuffle
BlockManager:
主:
BlockManagerMaster统筹管理所有数据存储
从:
BlockManagerSlaves真正数据写内存或磁盘
DiskStore:
管理磁盘
MemoryStore:
管理内存
ConnectionManager:
管理连接
BlockTransforService:
数据传输
管理范围:
广播变量,RDD持久化数据。
Driver进程:
1.MapOutputTrackMaster
2.BlockManagerMaster
3.DAGScheduler
4.TaskScheduler
5.ReceiverTracker
Master进程
Workers、waitingapps、waitingDrivers、assignedCores、assignedExecutor
Shuffle调优点:
1.shuffle内存比例
2.reducetask一次拉取数据大小
3.重试次数
4.重试间隔时间
5.Sortbypass方式
6.Sortbypass域值
7.Hash合并机制
8.Shuffle种类
9.写磁盘buffer大小
Shuffle文件寻址
1)MapOutputTracker
MapOutputTracker是Spark架构中的一个模块,是一个主从架构。
管理磁盘小文件的地址。
ØMapOutputTrackerMaster是主对象,存在于Driver中。
ØMapOutputTrackerWorker是从对象,存在于Excutor中。
2)BlockManager
BlockManager块管理者,是Spark架构中的一个模块,也是一个主从架构。
ØBlockManagerMaster,主对象,存在于Driver中。
BlockManagerMaster会在集群中有用到广播变量和缓存数据或者删除缓存数据的时候,通知BlockManagerSlave传输或者删除数据。
ØBlockManagerSlave,从对象,存在于Excutor中。
BlockManagerSlave会与Block
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- note32sparkHA 内存