Spark统一内存划分

1. Executor内存逻辑架构

在这里插入图片描述

  1. 堆内存,由JVM分配和回收,由spark.executor.memory控制大小,JVM中序列化的对象是以字节流形式,其占用内存大小可直接计算,对于非序列化对象,其占用的内存是通过周期性地采样近似估算,且被spark标记为释放的对象实例也有可能并没有被JVM回收,所以spark并不能准确记录实际可用堆内存,也就无法避免内存溢出
  2. 非堆内存,不受JVM管理,有两部分,其中一部分通常是yarn模式中通过spark.executor.memoryOverhead配置,该部分内存用于虚拟机自身的开销(字符串、NIO和其它一些本地开销);另一部分通过spark.memory.offHeap.enable/size结合配置,该部分由spark直接使用于存储内存和任务内存,从2.0开始不再依赖第三方内存系统Tachyon,而是基于JDK自带的Unsafe API实现堆外内存管理,堆外内存可以精确地申请和释放,减少了不必要的额外开销。
  3. 系统内存(systemMemory):这里指的是JVM可用的最大内存,可通过Runtime.getRuntime.maxMemory获得该值,系统内存并不等于分配的堆内存,由于年轻代GC采用复制算法,所以有一块survivor内存区需要保留,即systemMemory=堆内存-survivor
  4. 可用内存(usableMemory):这部分内存是用户代码能直接影响到的,可用内存=系统内存 - Reserved,其中Reverved为固定300M的保留内存,用于spark系统内部使用。
  5. 应用内存:主要用于存储用户代码生成的数据对象,这些数据对象被缓存之前就是处于应用内存空间
  6. 存储内存与执行内存:存储内存用于缓存数据,执行内存主要用于满足 Shuffle、 Join、 Sort、 Aggregation 等计算过程中对内存的需求,通过spark.memory.storageFraction控制两者比例,默认平分,两部分内存之间还可以进行动态占用:
    • 执行内存的空间被对方占用后,可让对方将占用的部分转存到硬盘,然后归还借用空间
    • 存储内存空间被对应占用后,无法让对方归还,因为shuffle过程中的很多因素无法实现

2. Executor 界面内存计算

在spark任务监控界面的Executors菜单中有一列“Storage Memory”显示当前“已用/总可用存储内存”情况,当使用静态内存机制时,此列总大小确实为存储内存,但使用统一内存机制时:

显示总内存 = 存储内存+执行内存

虽然两部分内存可以动态占用,但应该总不会把执行内存全部借光。此处分析统一内存该值是怎么个计算方式,既然知道显示的总内存是指存储内存和执行内存总和,那么结合逻辑架构图可以直观地看到组成方式:

显示总内存 =(堆内存储内存+堆内执行内存)+(堆外存储内存+堆外执行内存) 
           =(可用内存-应用内存)+ offHeap
		   = 可用内存 * spark.memory.fraction + offHeap
           =(系统内存-300M)* 0.6 + offHeap
           =(spark.executor.memory – survivor – 300M)* 0.6 + offHeap
           =(Runtime.getRuntime.maxMemory – 300M)* 0.6 + offHeap

这公式中survivor内存大小是核心,如果确定了就能确定总内存大小。按理解,可以通过NewRatio和SurvivorRatio计算出survivor大小的,但是通常指定了最大堆内存,但是jvm并不会初始化时就会申请到最大内存,则是动态增加的,所以survivor大小也只是一个估计值(约为90%)。可以通过指定Xms等于最大堆内存或者禁用UseAdaptiveSizePolicy,这样survivor就可以按比例计算出来。
另外需要注意一点,页面是通过请求”allexecutors”接口返回的数据,总内存对应的返回字段是maxMemory,单位是字节,而展现为GB时直接除1000而非1024,且最终结果小数位是五舍四入。

3. UnrollMemory理解

spark的rdd在缓存到存储内存之前,每条数据的对象实例都处于JVM进堆内内存的应用内存,即便同一个分区内的数据在内存空间也不是连续的(更可能不在同一物理节点?),具体分布由JVM管理,上层通过scala中的迭代器来访问。当rdd持久化储存内存之后,partition对应转换为block,此时数据在存储内存空间(堆内或堆外)中将连续的存储,这里将分区由不连续的存储空间转换为连续的存储空间的过程,就是unroll操作

在静态内存机制中,内存的构成部分有一个叫unroll内存,该部分从存储内存中独立划分 约占20%。但是在统一内存机制中,unroll内存却不存在了,其实在spark抽象的内存管理器(MemoryManger)中抽象了三个方法:

acquireStorageMemory
acquireUnrollMemory
acquireExecutionMemory

无论是统一内存(UnifiedMemoryManager)还是静态内存(StaticMemoryManager)按自己的逻辑实现了3个方法,统一内存中已经把存储内存和unroll内存合并,代码实现层面上UnifiedMemoryManager.acquireUnrollMemory其实也只是简单地调用了一下acquireStorageMemory

4. 参考

《spark sql内核剖析》
https://zhuanlan.zhihu.com/p/115888408
https://www.jianshu.com/p/87a36488993a
https://blog.youkuaiyun.com/lemonZhaoTao/article/details/81990408

<think>嗯,用户这次问的是Spark Executor内存配置和调优的问题。从引用内容来看,用户应该是在搭建或优化Spark作业时遇到了性能问题,特别是关于内存配置方面的疑问。 用户可能是个大数据工程师,正在处理实际生产环境中的Spark应用。ta引用的两段材料都指向内存配置的关键点:第一段提到executor-memory的设置范围和资源队列约束,第二段强调GC调优的重要性。看来用户不仅想知道基本配置,更关注如何避免OOM和性能优化。 我注意到用户特意强调“不是真正的需求”,说明ta希望我超越引用内容给出系统解答。用户深层需求可能是:如何根据具体业务场景配置内存?怎样平衡性能和资源消耗?遇到OOM时如何排查? 这个问题需要从四个层面展开:基础配置原则(内存大小设置)、内存结构划分(堆内堆外)、调优方法(GC策略/OFF_HEAP)、问题诊断(OOM分析)。尤其要强调Spark UnifiedMemoryManager的动态内存管理机制——这是很多文档没说清楚的关键点。 用户可能遇到的实际困难包括:不理解storage和execution内存的动态借用规则,或者混淆了off-heap和堆外存储的关系。在解释时要避免直接粘贴官方文档,用中文场景化说明,比如“当shuffle量很大时,如果execution内存不足会触发spill到磁盘导致性能下降”。 最好给出具体案例,比如配置8G内存时推荐的比例:4.5G堆内(storage:execution=3:5),0.5G堆外,预留3G给OS。这样用户能直接映射到自己的环境。还要提醒常见陷阱,比如配置了off-heap却忘记开启堆外内存开关(spark.memory.offHeap.enabled)。 最后补充诊断工具,这样用户遇到问题可以自己用Spark UI看内存压力指标。毕竟调参是个动态过程,授人以渔更重要。 </think>以下是关于Spark Executor内存配置与调优的系统性说明,结合核心参数、内存结构及优化策略: --- ### **一、Executor内存核心配置参数** | 参数 | 说明 | 建议值 | |------|------|--------| | `spark.executor.memory` | **堆内内存总量** | 4G~8G/Executor<br>(需满足:`num-executors × executor-memory ≤ 队列总内存 × 50%`) | | `spark.executor.memoryOverhead` | **堆外内存**(OS/JVM开销) | 堆内内存的10%~15%<br>(默认`max(384MB, 0.1×executor.memory)`) | | `spark.memory.fraction` | **统一内存池比例**(Execution + Storage) | 默认0.6(Spark 2.0+) | | `spark.memory.storageFraction` | **Storage内存占比**(不可被Execution抢占) | 默认0.5 | > 💡 **资源配置公式**: > `队列总内存 ≥ num-executors × (executor-memory + memoryOverhead)` > 避免超过资源队列总内存的1/3~1/2(共享环境)[^1] --- ### **二、Executor内存结构详解** ```mermaid graph LR A[Executor内存] --> B[堆内内存] A --> C[堆外内存] B --> D[统一内存池 60%] B --> E[用户内存 40%] D --> F[Storage内存 50%] --> 缓存RDD/Broadcast D --> G[Execution内存 50%] --> Shuffle/Join/Sort C --> H[JVM Off-Heap] --> Native操作/Netty缓存 ``` 1. **堆内内存(On-Heap)** - **统一内存池**:动态分配Execution/Storage,一方空闲时另一方可借用 - **用户内存**:存储用户代码中的数据结构(如UDF中的大对象) 2. **堆外内存(Off-Heap)** - 避免GC开销,用于Shuffle Netty传输、原生操作(如Tungsten优化) - 配置项:`spark.memory.offHeap.enabled=true` + `spark.memory.offHeap.size` --- ### **三、调优场景与策略** #### ▶ **常见问题与解决方案** | 问题现象 | 可能原因 | 优化手段 | |----------|----------|----------| | **Executor OOM** | 1. 用户内存溢出<br>2. Shuffle数据过大 | 1. 检查UDF中的对象大小<br>2. 增加`executor-memory`<br>3. 提升`memoryOverhead` | | **频繁Full GC** | 小对象过多/内存泄露 | 1. 启用堆外内存<br>2. GC调优:`-XX:+UseG1GC -XX:InitiatingHeapOccupancyPercent=35`[^2] | | **Shuffle失败** | Execution内存不足 | 1. 增加`spark.memory.fraction`至0.8<br>2. 减少`spark.memory.storageFraction` | #### ▶ **GC调优示例** ```bash # 在spark-submit中添加GC参数 --conf "spark.executor.extraJavaOptions=-XX:+UseG1GC \ -XX:InitiatingHeapOccupancyPercent=35 \ -XX:ConcGCThreads=4" ``` > **关键参数**: > - `UseG1GC`:G1垃圾回收器(适合大内存) > - `InitiatingHeapOccupancyPercent`:早触发GC避免Full GC --- ### **四、进阶优化技巧** 1. **数据倾斜应对** - 对倾斜Key加盐分散处理 - 启用`spark.sql.adaptive.enabled=true`(AQE自动优化) 2. **堆外内存优化** - 当使用PySpark时,增加`memoryOverhead`(Python进程独立于JVM) - 启用Tungsten引擎:`spark.sql.tungsten.enabled=true` 3. **监控工具** - **Spark UI**:观察各Executor的GC时间/内存压力 - **GC日志分析**:添加`-XX:+PrintGCDetails -XX:+PrintGCTimeStamps` --- ### **五、配置案例** ```bash # 典型生产环境配置示例 spark-submit \ --executor-memory 8G \ # 堆内内存 --memoryOverhead 2G \ # 堆外内存(8G的25%) --conf spark.memory.fraction=0.8 \ --conf spark.memory.storageFraction=0.3 \ --conf spark.executor.extraJavaOptions="-XX:+UseG1GC" ``` > 📌 **黄金法则**:**先监控后调优** > 通过Spark UI的`Executor`页签观察: > - `Storage Memory`使用率 > - `GC time`是否超过任务时间的10% > - `Shuffle Spill`次数(内存不足时溢出磁盘) ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值