Spark内存管理

Spark 内存管理

一、统一内存管理

1、堆内内存(on-heap)

  • 大小可由 spark 作业提交参数 -executor-memory,或配置参数 spark.executor.memory 来手动配置
  • executor 进程 JVM 可用内存,由 JVM 统一管理,executor 内并发任务共享
1.1 空间分配
  • 30% 存储内存(storge):存储 rdd 缓存数据、broadcast 数据(数据是否序列化由缓存级别决定)
  • 30% 执行内存(execution):shuffle 执行过程产生的中间数据
  • 40% 其他空间(other):用户自定义数据结构、spark 内部元数据(未缓存的 rdd 迭代器结构 record 数据在此存储)
  • 300M 预留空间:同 other 空间
  • 序列化数据占用内存空间小,但反序列化更耗 CPU 资源
  • 序列化后的数据可直接计算得到占用的空间大小,非序列化数据需要周期性采样预估所需空间
1.2 申请与释放
  • 申请:
  1. Spark 在代码中 new 一个对象实例
  2. JVM 从堆内内存分配空间,创建对象并返回对象引用
  3. Spark 保存该对象的引用,记录该对象占用的内存
  • 释放:
  1. Spark 记录该对象释放的内存,删除该对象的引用
  2. 等待 JVM 的 GC 机制释放该对象占用的内存
  3. 存在 spark 标记为释放,但并未被 GC 的对象

2、堆外內存(off-heap)

  • 直接向操作系统申请和释放的内存空间
  • 参数 spark.memory.offHeap.size,默认关闭,需将参数 spark.memory.offHeap.enabled 配置为 true
  • 参数 spark.executor.memoryOverhead,默认开启,默认大小为堆内内存的 0.1 倍与 384M 的较大值
2.1 空间分配
  • 50%存储内存(storge):同堆内,序列化后的二进制数据
  • 50%执行内存(execution):同堆内,序列化后的二进制数据

3、动态占用机制

  • 存储内存与执行内存公用一块内存空间,由参数 spark.storage.storageFraction 配置默认比例
  • 当默认空间不足时(指不足以放下一个完整的 Block ),二者可互相借用:
  1. 执行内存被存储内存借用,执行内存可要求存储内存归还借用空间
  2. 反之,存储内存被执行内存借用,存储内存不可要求执行内存归还借用空间
  3. 这是由于执行内存用来存储 shuffle 的中间数据,不可控因素太多

二、存储内存管理

1、RDD 缓存机制

  • task 启动时会检查 rdd 是否被持久化,若无则会检查 checkpoint 或按血缘重新计算
  • cache()persist() 方法可在内存或磁盘中持久化或缓存 rdd 数据
  • cache() 默认缓存级别 rdd 为 MEMORY_ONLY,DataSet 为 MEMORY_AND_DISK
  • 缓存级别:
    MEMORY_ONLY、MEMORY_AND_DISK、DISK_ONLY、MEMORY_ONLY_SER、MEMORY_AND_DISK_SER、DISK_ONLY_SER、OFF_HEAP、MEMORY_ONLY2 ……
  • 源码中由以下5个属性的不同组合定义缓存级别:
    private var _useDisk: Boolean, // 磁盘
    private var _useMemory: Boolean, // 指堆内内存
    private var _useOffHeap: Boolean, // 堆外内存
    private var _deserialized: Boolean, // 是否为非序列化
    private var _replication: Int = 1 // 副本个数,大于1则会在其他节点做远程备份
    
  • RDD 的缓存由 spark 的 storge 模块完成,该模块主要负责实现计算过程中产生的数据在内存或磁盘、本地或远程的存取过程
    1. driver 和 executor 端会各自启动 BlockManager 组成主从结构,driver 端为 master,executor 端为 slave,以 block 为存储单元
    2. rdd 的每个 partition 经过处理后都会生成一个唯一对应的 block,block 格式为:rdd_RDD-ID_PARTITION-ID
    3. driver 的 master 负责管理和维护作业中所有的 block 元数据信息
    4. executor 端的 slave 则负责将 block 的更新状态上报 master ,并接收 master 的命令,如新增或删除一个 block

2、RDD 缓存流程

  1. 缓存前 rdd 数据以 Iterator 结构存储,迭代器中各个 partition 每条数据存储为一个 record,record 可能是序列化或非序列化的,占用堆内内存 other 空间,同一 partition 中的 record 并不连续
  2. rdd 做缓存后, partition 转化为 block,占用内存中的一块连续空间,partition 转化为 block 的过程称为 Unroll
  3. 计算过程中无法保证能够一次存储 Iterator 中的全部数据,每次 Unroll 都需要向 MemeryManager 申请 Unroll空间 做临时占位,空间足够则 Unroll 成功,反之 Unroll 失败
    • 序列化的 partition,所需空间可累加计算,直接一次申请
    • 非序列化的 partition,需要在遍历 record 时逐条采样估算所需空间进行申请,空间不足时可中断,释放已占用的空间
    • 最终 Unroll 成功,则会将 Unroll空间 转换为正常的 rdd 缓存空间
  4. block 由缓存级别确定是否序列化,非序列化 block 使用 DeserializedMemoryEntry 数据结构,用一个数组存储所有数据;序列化 block 使用 SerializedMemoryEntry 数据结构,用 ByteBuffer 存储二进制数据
  5. executor 使用 LinkedHashMap 来管理堆内堆外内存中所有的 block 对象实例,对 LinkedHashMap 的增删间接记录了内存的申请和释放

3、淘汰与落盘

  • 当 executor 中有新的 block 需要缓存,但剩余空间不足且无法启用动态占用机制时,就需要对 executor 维护的 LinkedHashMap 中旧的 block 进行淘汰(Eviction),若被淘汰的 block 缓存级别包含磁盘,则需要对 block 数据落盘(Drop)并在更新 block 信息,否则直接删除
  • 淘汰规则:
    1. 新旧 block 有相同的 MemoryMode,即同属堆内或堆外
    2. 新旧 block 不能属于同一 rdd,避免循环淘汰
    3. 旧 block 不能处于被读状态,避免一致性问题
    4. 遍历 LinkedHashMap,按最近最少使用(LRU)顺序淘汰,LRU 为 LinkedHashMap 的特性

三、执行内存管理

1、shuffle Write

  • 根据 map 阶段排序方式不同有两种情况:
    1. 选择普通排序,会调用 ExternalSorter 进行外排,主要使用堆内执行空间存储数据
    2. 选择 Tungsten 排序,会调用 ShuffleExternalSorter 直接对序列化的数据排序,可使用堆内或堆外执行空间,取决于是否启用堆外内存及堆外执行空间是否充足
  • Tungsten 排序优化:
    • 钨丝计划,Databricks 公司提出的优化 CPU 和内存使用的计划,突破 JVM 在性能上的限制和弊端,spark 会根据 shuffle 情况自动选择是否启用此优化
    • Tungsten 对内存做了进一步抽象,似的 spark 在 shuffle 过程中不再关心数据是在用堆内还是堆外内存上
    • 具体即在 MemoryManager 基础上使用叶式内存管理机制,每个内存页用一个 MemoryBlock 来定义,用 Object objlong offset 这两个变量统一标识一个内存页在系统内存中的地址,并使用页表(pageTable)来管理每个 task 申请到的内存页
      1. 堆内内存:long型 数组的形式分配内存,obj 保存对该数组的引用,offset 保存该数组在 JVM 中的初始偏移地址
      2. 堆外内存:直接在系统申请内存块,obj 值为 nulloffset 保存该内存块在系统中的64位绝对地址
    • 页式管理下的所有内存用 64位的逻辑地址表示,由页号和页内偏移量组成:
      1. 13位的页号:唯一标识一个内存页, Spark 在申请内存页之前要先申请空闲页号
      2. 51位的页内偏移量:使用内存页存储数据时,数据在页内的偏移地址
    • Spark 可以用 64位逻辑地址的指针定位到堆内或堆外的内存,整个 Shuffle Write 排序的过程只需要对指针进行排序,并且无需反序列化,整个过程非常高效,对于内存访问效率和 CPU 使用效率带来了明显的提升

2、shuffle Read

  • reduce 阶段会将数据交给 Aggregator 做聚合,若最终需要排序还要再交给 ExternalSorter 做排序,两项操作均需占用堆内执行空间
  • 上述两项操作中 spark 会使用一种哈希表 AppendOnlyMap 存储数据,但并非 shuffle 过程中所有数据都能存入此哈希表,当周期性采样预估的数据量大到无法再从申请到新的空间时,会将数据全部存储到磁盘,此过程称为溢存(spill),所有溢存的数据最后会进行归并(merge
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值