【Spark】Spark 内存管理 - 存储内存

  前两天的文章学习了堆内内存、堆外内存以及内存空间的分配,没看过的可以看前面的文章。
【Spark】Spark 的堆内内存和堆外内存
【Spark】Spark 内存分配
今天学习 Spark 的内存管理。内存管理分为存储内存和执行内存。今天先学习存储内存。

1.RDD 的持久化机制

弹性分布式数据集(RDD)作为 Spark 最根本的数据抽象,其特点如下:

  • 是只读的分区记录(Partition)的集合,只能基于在稳定物理存储中的数据集上创建,或者在其他已有的 RDD 上执行转换(Transformation)操作产生一个新的 RDD。
  • 转换后的 RDD 与原始的 RDD 之间产生的依赖关系,构成了血缘(Lineage)。血缘保证了每一个 RDD 都可以被重新恢复。
  • RDD 的所有转换都是惰性的,即只有当一个返回结果给 Driver 的行动(Action)发生时,Spark 才会创建任务读取 RDD,然后真正触发转换的执行。

Task 在启动之初读取一个分区时会进行持久化或缓存:

  • 先判断这个分区是否已经被持久化,如果没有则需要检查 Checkpoint 或按照血缘重新计算。
  • 如果一个 RDD 上要执行多次行动,可以在第一次行动中使用 persist 或 cache 方法,在内存或磁盘中持久化或缓存这个 RDD,从而在后面的行动时提升计算速度。
  • cache 方法是使用默认的 MEMORY_ONLY 的存储级别将 RDD 持久化到内存,故缓存是一种特殊的持久化。 堆内和堆外存储内存的设计,便可以对缓存 RDD 时使用的内存做统一的规划和管理。

RDD 的持久化的实现是靠 Storage 模块:

  • 由 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 )。
  • Driver 端的 Master 负责整个 Spark 应用程序的 Block 的元数据信息的管理和维护,而 Executor 端的 Slave 需要将 Block 的更新等状态上报到 Master,同时接收 Master 的命令,例如新增或删除一个 RDD。

在这里插入图片描述

在对 RDD 持久化时,Spark 规定了 MEMORY_ONLY、MEMORY_AND_DISK 等7种不同的存储级别,而存储级别是以下5个变量的组合:

class StorageLevel private(
    private var _useDisk: Boolean, //磁盘
    private var _useMemory: Boolean, //这里其实是指堆内内存
    private var _useOffHeap: Boolean, //堆外内存
    private var _deserialized: Boolean, //是否为非序列化
    private var _replication: Int = 1 //副本个数
)

Spark 中 7 种存储级别如下:

持久化级别含义
MEMORY_ONLY以非序列化的 Java 对象的方
式持久化在 JVM 内存中。如
果内存无法完全存储 RDD 所
有的 partition,那么那些没
有持久化的 partition 就会在
下一次需要使用它们的时候,
重新被计算
MEMORY_AND_DISK同上,但是当某些 partition
无法存储在内存中时,会持
久化到磁盘中。下次需要使
用这些 partition时,需要从
磁盘上读取
MEMORY_ONLY_SER同 MEMORY_ONLY,但是会
使用 Java 序列化方式,将
Java 对象序列化后进行持久
化。可以减少内存开销,但
是需要进行反序列化,因此
会加大 CPU 开销
MEMORY_AND_DISK_SER同 MEMORY_AND_DISK,但
是使用序列化方式持久化 Java
对象
DISK_ONLY使用非序列化 Java 对象的方
式持久化,完全存储到磁盘上
MEMORY_ONLY_2
MEMORY_AND_DISK_2 等
如果是尾部加了 2 的持久化级
别,表示将持久化数据复用一
份,保存到其他节点,从而在
数据丢失时,不需要再次计算,
只需要使用备份数据即可

通过对数据结构的分析,可以看出存储级别从三个维度定义了 RDD 的 Partition(同时也就是 Block)的存储方式:

  • 存储位置:磁盘/堆内内存/堆外内存。如 MEMORY_AND_DISK 是同时在磁盘和堆内内存上存储,实现了冗余备份。OFF_HEAP 则是只在堆外内存存储,目前选择堆外内存时不能同时存储到其他位置。
  • 存储形式:Block 缓存到存储内存后,是否为非序列化的形式。如 MEMORY_ONLY 是非序列化方式存储,OFF_HEAP 是序列化方式存储。
  • 副本数量:大于 1 时需要远程冗余备份到其他节点。如 DISK_ONLY_2 需要远程备份 1 个副本。
2.RDD 的缓存过程

RDD 在缓存到存储内存之前:

  • Partition 中的数据一般以迭代器(Iterator)的数据结构来访问;
  • 通过 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 的存储空间,如下图所示。

在这里插入图片描述

在静态内存管理时,Spark 在存储内存中专门划分了一块 Unroll 空间,其大小是固定的,统一内存管理时则没有对 Unroll 空间进行特别区分,当存储空间不足时会根据动态占用机制进行处理。

3.淘汰与落盘

由于同一个 Executor 的所有的计算任务共享有限的存储内存空间,当有新的 Block 需要缓存但是剩余空间不足且无法动态占用时,就要对 LinkedHashMap 中的旧 Block 进行淘汰(Eviction),而被淘汰的 Block 如果其存储级别中同时包含存储到磁盘的要求,则要对其进行落盘(Drop),否则直接删除该 Block。

存储内存的淘汰规则为:

  • 被淘汰的旧 Block 要与新 Block 的 MemoryMode 相同,即同属于堆外或堆内内存;
  • 新旧 Block 不能属于同一个 RDD,避免循环淘汰;
  • 旧 Block 所属 RDD 不能处于被读状态,避免引发一致性问题;
  • 遍历 LinkedHashMap 中的 Block,按照最近最少使用(LRU)的顺序淘汰,直到满足新 Block 所需的空间。

落盘的流程则比较简单,如果其存储级别符合 _useDisk 为 true 的条件,再根据其 _deserialized 判断是否是非序列化的形式,若是则对其进行序列化,最后将数据存储到磁盘,在 Storage 模块中更新其信息。

欢迎关注。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值