Spark Executor内存Overhead耗尽原因及任务执行差异排查
解答
一、Spark YARN Executor内存Overhead的具体内容
spark.yarn.executor.memoryOverhead是为Executor进程的非堆内存开销预留的额度,这部分内存不包含在spark.executor.memory配置的JVM堆内存内,YARN会监控容器总内存(堆内存+Overhead)的使用情况,具体涵盖以下几类开销:
- JVM原生内存开销:包括JVM代码缓存(存储JIT编译后的机器码)、垃圾回收器工作内存(如标记阶段的临时数据存储)、线程栈内存(单线程默认栈大小1MB,线程数量越多开销越大)、直接内存(NIO ByteBuffer等绕过JVM堆的内存分配)。
- 第三方库与Spark组件的原生内存使用:即使JDBC驱动是纯托管代码,若驱动内部使用NIO直接内存、或依赖的序列化库(如Kryo)处理数据时用到原生内存,都会计入Overhead;此外Spark的Netty网络组件处理IO时也会占用直接内存。
- 进程级系统开销:操作系统为Executor进程分配的页表、文件描述符关联内存等底层资源占用。
- 瞬时内存波动:任务执行过程中的临时内存峰值,比如JDBC读取时的结果集缓存、数据序列化/反序列化的临时内存占用,这些峰值可能超出堆内存预估范围,需要Overhead来承接。
Spark默认Overhead取值为max(384MB, 0.1*spark.executor.memory),它是YARN容器总内存的组成部分,一旦容器物理内存使用(含所有上述开销)超过spark.executor.memory + spark.yarn.executor.memoryOverhead的总和,YARN就会触发容器杀死操作。
二、部分Executor执行同一任务失败、重试成功的原因
这种现象源于内存使用的随机性与节点资源差异,具体原因包括:
- 节点资源竞争差异:不同YARN节点的系统负载不同,部分节点可能同时运行NodeManager、其他业务容器等进程,可用物理内存余量更少。当Executor调度到这类节点时,即使任务本身内存使用正常,也可能因节点整体内存紧张被YARN判定为超量使用而杀死;重试时任务可能被调度到资源更充足的节点,从而成功执行。
- 任务执行的内存波动:同一查询在不同Executor上执行时,内存使用会存在瞬时波动——比如GC触发时机不同,某个Executor执行时恰好遇到GC的内存峰值;或JDBC读取结果集的加载顺序、数据分片的内存占用分布不同,导致瞬时内存超出容器限制。重试时这些随机因素发生变化,内存峰值未超过阈值,任务即可成功。
- Executor状态差异:运行过一段时间的Executor可能存在堆内存碎片、残留临时对象,可用内存比新启动的Executor更少。重试时若调度到新启动的Executor,或原有Executor经过Full GC释放了足够内存,就能满足任务的内存需求。
- YARN监控的瞬时误差:YARN通过操作系统的进程内存统计(如RSS)监控容器内存,存在一定延迟或瞬时误差。若某个时刻任务的内存峰值刚好被监控捕获,就会触发容器杀死;重试时该峰值未被捕获或峰值更低,任务得以完成。
额外优化建议
你的代码使用Scala并行集合在Driver端并行提交多个Spark Job,这种方式会导致多个Job同时抢占Executor资源,内存需求叠加后更容易触发YARN内存限制。建议改用Spark分布式执行框架处理查询:将查询列表转为RDD/DataFrame,利用Spark的分区并行执行每个查询的读取与写入,避免Driver端并行带来的资源竞争问题。
内容的提问来源于stack exchange,提问作者Alexey
相关产品推荐
相关产品推荐

