Spark 内存管理

Spark 内存管理详解


通常我们讨论的是Executor中的内存管理,自1.6版本后spark的内存分为两个部分, 堆内内存和堆外内存。下面详细针对这两个部分进行详细的介绍。

堆内内存

堆内内存的大小,由spark应用程序启动时的--executor-meory或spark.executor.memory参数指定的。 而这一部分内存又分为3大部分:

  • Reserved Memory: 这一部分使我们无法使用的,spark内部保留的部分,会存储一些spark的内部对象等内容。默认大小实是300M,无法通过外部参数更改。
  • User Memory: 用户在程序中创建的对象存储等一些列非spark管理的内存开销都占用这一部分内存。可以这么认为,这是程序运行是用户可以主导的空间,叫做用户操作空间。默认大小是: (Java Heap - ReservedMemory) * 0.4
  • Spark Memory: 系统框架运行时所需要使用的空间,内部又分为两个部分,分别是Storage Memory和Execution Memory。
    • 总默认大小是:(Java Heap - ReservedMemory) * spark.memory.fraction 其中spark.memory.fraction默认大小为0.6
    • 两个子部分Storage Memory和Execution Memory,分别各占0.5,可以使用spark.memory.storageFraction参数来控制storage部分和execution部分的内存占比。
    • Storage Memory主要用来存储我们cache的数据和临时空间序列化时unroll数据
    • Execution Memory则是sparkTask执行时使用的内存(比如shuffle时排序就需要大量的内存)
    • 当然这两部分的内存划分也不是十分的严格,存在一个动态占用机制,具体规则见后文

见下图:

Storage内存和Execution内存之间互相占用的机制如下:

  • 设定基本的存储内存和执行内存区域(spark.storage.storageFraction 参数),该设定确定了双方各自拥有的空间的范围
  • 双方的空间都不足时,则存储到硬盘;若己方空间不足而对方空余时,可借用对方的空间;(存储空间不足是指不足以放下一个完整的 Block)
  • 执行内存的空间被对方占用后,可让对方将占用的部分转存到硬盘,然后"归还"借用的空间
  • 存储内存的空间被对方占用后,无法让对方"归还",因为需要考虑 Shuffle 过程中的很多因素,实现起来较为复杂

Storage内存管理

当我们需要在计算过程中对一份中间结果使用多次的时候,通常我们需要将中间计算结果缓存下来,spark中提供了cache和persist方法,来实现在内存缓存或磁盘上持久化这个RDD。

RDD的持久化由Spark的Storage模块负责,实现了RDD与物理存储的解耦合。Storage模块负责管理Spark在计算过程中产生的数据,将那些在内存或磁盘、在本地或远程存取数据的功能封装了起来。在具体实现时Driver端和Executor端的Storage 模块构成了主从式的架构,即Driver端的BlockManager为Master,Executor端的 BlockManager为Slave。Storage模块在逻辑上以Block为基本存储单位RDD的每个 Partition经过处理后唯一对应一个Block(BlockId的格式为rdd_RDD-ID_PARTITION-ID)。Master负责整个Spark应用程序的Block的元数据信息的管理和维护,而 Slave需要将Block的更新等状态上报到Master,同时接收Master的命令,例如新增或删除一个RDD。 示意图如下:

RDD在计算过程中,每个task内部计算一个partition,计算过程中产生的Record其实都是存储在UserMemory中的。

RDD在缓存到内存之后,Partition被转换成Block,Record在堆内或堆外存储内存汇总占用一块连续的空间。将Partition由不连续的存储空间转换为连续存储空间的过程,Spark称之为"展开"(Unroll)。每个Executor的Storage模块用一个链式 Map 结构(LinkedHashMap)来管理堆内和堆外存储内存中所有的Block对象的实例,对这个LinkedHashMap 新增和删除间接记录了内存的申请和释放。

执行内存管理

对任务间内存分配

Executor内运行的任务同样共享执行内存,Spark用一个HashMap结构保存了任务到内存逍遥的映射。每个任务可占用执行内存大小的范围为1/2N~1/N,N为当前Executor内正在运行的任务个数。每个任务在启动之时,要向MemoryManager请求申请最少为1/2N的执行内存,如果不能被满足要求则该任务被阻塞,直到有其他任务释放了足够的执行内存,该任务才可以被唤醒。

Shuffle内存占用

执行内存主要用来存储任务在执行Shuffle时占用的内存。

  1. 在对reduce端的数据进行聚合时,要将数据交给Aggregator处理,在内存中处理数据是占用堆内之星空间。
  2. 如果需要进行最终结果排序,则要再次将数据交给ExternalSorter处理,占用堆内执行空间。

在Aggregator和ExternalSorter中,Spark会使用一种较AppendOnlyMap的哈希表在堆内执行内存中存储数据,但在Shuffle过程中并不能将所有数据都保存到该哈希表中,当这个哈希表大到一定程度,无法再从MemoryManager中申请到新的执行内存时,Spark就会将其全部内容存储到磁盘文件中,这个过程被称为溢存(Spill),溢存到磁盘的文件最后会被归并(Merge)。

猜你喜欢

转载自my.oschina.net/nalenwind/blog/1788777