Spark内存管理
- 堆内内存,JVM管理
- 堆外内存,操作系统
为什么需要堆外内存?
因为jvm存在回收不及时,不能精确回收估计内存的问题,容易造成OMM.
同时也避免了频繁的GC带来的程序停顿,当然JVM也是由优势的。
为什么无法精确回收?
因为一个对象创建后,保存在堆中。清除是由JVM处理的,spark只能标记一下。
序列化的对象spark可以精确的计算,但是非序列化的对象不能精确估计。所以在内存分配中会预留一小部分方式,防止计算的误差导致的OMM.
内存管理方式
静态管理
这种方法是旧的方法容易造成涝的涝死,旱的旱死的尴尬局面,所以现在已不用,作为了解。
大分类
如上图:
Storge:用于缓存和存储广播变量
- unroll用于从other空间将不连续的用户创建的对象或者spark的元数据对象展开为连续的序列化数据的临时空间。如果申请不足就失败缓存
excution: 用于存储shuffle过程中的数据。
other:用于存储一些用户自定义的对象和spark的元数据
统一管理方式
为了解决上面容易造成涝的涝死,旱的旱死的尴尬局面,诞生了一个可以抢占对方资源的功能。在存储和执行内存间,如果自己的内存不足了就可以抢占对方的资源。
- 执行内存占用的存储内存的空间是不可以归还的,因为涉及到shuffle的很多设计上的问题
- 执行内存被存储内存占用的空间是可以归还的,因为存储空间没有执行内存重要
缓存的过程
缓存的架构也是主从结构,BlockMangerMaster负责下达新增删除的指令和元数据管理,BlockManager的负责管理缓存和报告自己的状态给Master
存储的基本单位和数据结构
- 以Block为一个单位,即是一个分区计算的数据
- 用LinkHashMap管理,key 为blockId,blockid格式为 rdd_RDD-ID_PARTITION-ID
缓存过程的关键步骤
- 申请展开的区域(unroll),因为在other区域的数据是不连续的,缓存为了方便后面的使用需要将它组装成连续的区域
- 用分区的迭代器开始访问other区的数据
- 如果是序列化数据,申请空间是可以一次性的
- 如果非序列化数据就只能访问一条存储一条了
RDD 在缓存到存储内存之前,Partition 中的数据一般以迭代器(Iterator)的数据结构来访问,这是 Scala 语言中一种遍历数据集合的方法。通过 Iterator 可以获取分区中每一条序列化或者非序列化的数据项(Record),这些 Record 的对象实例在逻辑上占用了 JVM 堆内内存的 other 部分的空间,同一 Partition 的不同 Record 的存储空间并不连续。
RDD 在缓存到存储内存之后,Partition 被转换成 Block,Record 在堆内或堆外存储内存中占用一块连续的空间。将Partition由不连续的存储空间转换为连续存储空间的过程,Spark称之为展开(Unroll)**。
Block 有序列化和非序列化两种存储格式,具体以哪种方式取决于该 RDD 的存储级别。非序列化的 Block 以一种 DeserializedMemoryEntry 的数据结构定义,用一个数组存储所有的对象实例,序列化的 Block 则以 SerializedMemoryEntry的数据结构定义,用字节缓冲区(ByteBuffer)来存储二进制数据。每个 Executor 的 Storage 模块用一个链式 Map 结构(LinkedHashMap)来管理堆内和堆外存储内存中所有的 Block 对象的实例,对这个 LinkedHashMap 新增和删除间接记录了内存的申请和释放。
因为不能保证存储空间可以一次容纳 Iterator 中的所有数据,当前的计算任务在 Unroll 时要向 MemoryManager 申请足够的 Unroll 空间来临时占位,空间不足则 Unroll 失败,空间足够时可以继续进行。
对于序列化的 Partition,其所需的 Unroll 空间可以直接累加计算,一次申请。
对于非序列化的 Partition 则要在遍历 Record 的过程中依次申请,即每读取一条 Record,采样估算其所需的 Unroll 空间并进行申请,空间不足时可以中断,释放已占用的 Unroll 空间。
如果最终 Unroll 成功,当前 Partition 所占用的 Unroll 空间被转换为正常的缓存 RDD 的存储空间,如下图所示。
淘汰与落盘
如果在缓存过程中发现空间不够了,怎么办?
淘汰旧的数据或者将允许落盘的数据写入磁盘(配置了可以落盘的持久化)
淘汰的原则:
- 不淘汰同一个rdd的不通分区,因为缓存最小单位是分区,避免循环淘汰
- 如果能落盘,先落盘。
- LRULRU算法算法计算淘汰片
- 淘汰的片不能是正在读的数据,避免一致性问题
- 淘汰的片要与缓存的片缓存模式是一样的,即比如:Memory_Only
执行内存
用 AppendOnlyMap 来存储 Shuffle 过程中的数据,在 Tungsten 排序中甚至抽象成为页式内存管理,开辟了全新的 JVM 内存管理机制。
https://zhuanlan.zhihu.com/p/161269766