zoukankan      html  css  js  c++  java
  • 一篇文章搞清spark内存管理

    ​ 在执行Spark的应用程序时,Spark集群会启动Driver和Executor两种JVM进程,前者为主控进程,后者负责执行具体的计算任务。由于Driver的内存管理相对简单,本文主要对Executor的内存管理进行分析,下文中的Spark内存均特指Executor的内存。

    1.堆内存和堆外内存

    ​ 由于spark还是JVM进程,所以Executor 的内存管理建立在 JVM 的内存管理之上,因此存在着堆的概念,但是我们知道,堆内存受到 JVM 统一管理,它的GC是有一定算法逻辑的,当spark任务需要申请和释放内存的时候,并不是那么的自由灵活,因此spark引入了堆外内存,使之可以直接在工作节点的系统内存中开辟空间,进一步优化了内存的使用,不受JVM GC的管控,放飞自我。

    1.1 堆内存

    Executor 内运行的并发任务共享 JVM 堆内存,他们按照用途共分为3类。

    • Storage:缓存 RDD 数据和广播变量数据
    • Execution:执行Shuffle时占用的内存
    • 剩余空间:不做特殊规划,存储Spark内部的对象实例,和用户定义的Spark应用程序中的对象实例

    spark submit参数配置:

    --executor-memory 或者 spark.executor.memory

    Spark虽然不能精准控制堆内内存的申请和释放,但通过对存储内存和执行内存各自独立的规划管理,可以决定是否要在存储内存里缓存新的 RDD,以及是否为新的任务分配执行内存,在一定程度上可以提升内存的利用率,比如更改参数配置spark.storage.memoryFraction可调整storage占的百分比,spark.shuffle.memoryFraction可调整Execution占的百分比

    1.2 堆外内存

    为了进一步优化内存的使用以及提高Shuffle时排序的效率,Spark引入了堆外(Off-heap)内存,使之可以直接在工作节点的系统内存中开辟空间,存储经过序列化的二进制数据。

    默认情况下堆外内存并不启用,启用参数:spark.memory.offHeap.enabled

    堆外内存大小参数:spark.memory.offHeap.size。除了没有 other空间,堆外内存与堆内内存的划分方式相同,所有运行中的并发任务共享存储内存和执行内存。

    JVM 对于内存的清理是无法准确指定时间点的,因此无法实现精确的释放。堆外内存由于序列化的数据占用的空间可以被精确计算,所以相比堆内内存来说降低了管理的难度,也降低了误差

    2. 内存空间分配

    2.1 早期的静态内存管理

    spark 1.6之前的内存管理较为简单,采用静态内存管理机制,用户可在启动前进行配置各个区域大小占比,但是运行过程中,各内存区间的大小均是固定的。堆内内存默认 Storage:Execution:Other=6:2:2,堆外内存没有other区,默认Storage:Execution=1:1。by the way,由于 Spark 堆内内存大小的记录是不准确的,Storage 内存和 Execution 内存都有预留空间防止OOM。堆外内存不需要

    2.2 统一内存管理机制

    Spark1.6之后引入的统一内存管理机制,与静态内存管理的区别在于存储内存和执行内存共享同一块空间,可以动态占用对方的空闲区域。

    统一内存管理机制堆内内存模型如图:

    统一内存管理机制堆外内存模型如图:

    在运行前,用户设定基本存储内存(Storage)和执行内存(Execution)的比例,方式和静态内存管理机制一致,在此基础上,统一内存管理做了如下优化(注意只在Storage和Execution之间开了这个窗口,other还是固定的):

    • 双方的空间都不足时,则存储到硬盘;若己方空间不足而对方空余时,可借用对方的空间;(存储空间不足是指不足以放下一个完整的 Block)
    • Execution执行内存的空间被对方占用后,可让对方将占用的部分转存到硬盘,然后"归还"借用的空间
    • 存储内存的空间被Execution执行内存占用后,无法让对方"归还",因为需要考虑 Shuffle 过程中的很多因素,实现起来较为复杂

    由此可见,Execution由于计算资源比较重要,因此凭实力借的区域在运行期间可以不还,当要收回自己空间时候,对方反抗不了。

    3. 存储内存(Storage)缓存RDD怎么存储

    ​ 一个RDD上要执行多次行动,可以在第一次行动中使用persist或cache方法,在内存或磁盘中持久化或缓存这个RDD,从而在后面的行动时提升计算速度。cache方法是使用默认的MEMORY_ONLY的存储级别将RDD持久化到内存,故缓存是一种特殊的持久化。堆内和堆外存储内存的设计,便可以对缓存RDD时使用的内存做统一的规划和管理。

      RDD的缓存和持久化persist由Spark的Storage模块负责,实现了RDD与物理存储的解耦合。在存储上,Storage模块在逻辑上以Block为基本存储单位,RDD的每个Partition经过处理后唯一对应一个Block(BlockId 的格式为 rdd_RDD-ID_PARTITION-ID )。

    在具体实现时Driver端和Executor端的Storage模块构成了主从式的架构,即Driver端的BlockManager为Master,Executor端的BlockManager为Slave。Driver端的Master负责整个Spark应用程序的Block的元数据信息的管理和维护,而Executor端的Slave需要将Block的更新等状态上报到Master,同时接收Master的命令,例如新增或删除一个RDD。

    4. 执行内存(Execution)怎么管理

    执行内存主要用来存储任务在执行 Shuffle 时占用的内存,Shuffle 是按照一定规则对 RDD 数据重新分区的过程,主要关注 Shuffle 的 Write 和 Read 两阶段对执行内存的使用。

    • Shuffle Write
    1. 若在 map 端选择普通的排序方式,会采用 ExternalSorter 进行外排,在内存中存储数据时主要占用堆内执行空间。

    2. 若在 map 端选择 Tungsten 的排序方式,则采用 ShuffleExternalSorter 直接对以序列化形式存储的数据排序,在内存中存储数据时可以占用堆外或堆内执行空间,取决于用户是否开启了堆外内存以及堆外执行内存是否足够。

    • Shuffle Read
    1. 在对 reduce 端的数据进行聚合时,要将数据交给 Aggregator 处理,在内存中存储数据时占用堆内执行空间。

    2. 如果需要进行最终结果排序,则要将再次将数据交给 ExternalSorter 处理,占用堆内执行空间。

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

    Spark 的存储内存和执行内存有着截然不同的管理方式:对于存储内存来说,Spark 用一个 LinkedHashMap 来集中管理所有的 Block,Block 由需要缓存的 RDD 的 Partition 转化而成;而对于执行内存,Spark 用 AppendOnlyMap 来存储 Shuffle 过程中的数据,在 Tungsten 排序中甚至抽象成为页式内存管理,开辟了全新的 JVM 内存管理机制。

  • 相关阅读:
    5的阶乘以及任意输入一个数的阶乘
    继入门程序后的第一篇函数调用的小程序 比较两数大小
    计算机网络01-计算机网络与因特网
    2021春招冲刺-1227 数组去重 | 响应式布局 | 媒体查询 |浏览器帧
    2021春招冲刺-1225 TCP与UDP | 单例模式 | 回流与重绘
    2021春招冲刺-1223 进程线程的通信 | 字符串是否有效 | 数组转换与展平
    2021春招冲刺-1221 进程与线程的区别 | 进程的切换 | 单链表是否相交 | 元素水平/垂直居中的方式
    左边固定,右边自适应解决方案
    mock 模拟数据在框架中的简单使用
    一个页面从输入url到加载到内容,这个过程经历了什么
  • 原文地址:https://www.cnblogs.com/valjeanshaw/p/12581507.html
Copyright © 2011-2022 走看看