订阅更新

Executor频繁被杀——Spark内存模型和调优思路

Spark手册 第 13 题 约 3 分钟读完

本文目录
  1. 先说结论
  2. Executor内存的完整分配
  3. 三种“被杀”场景和解决方案
  4. 实战经验:我的调优检查清单
  5. 总结

先说结论

Executor被杀的常见原因有三个:堆内存OOM、堆外内存超限、GC停顿过久。调优的核心是理解内存的分配结构,对症下药。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

第1篇讲了OOM的排查,这一篇深入讲Spark的内存模型和系统性的调优思路。很多人遇到Executor被杀就无脑加内存,但有时候加内存也解决不了问题。今天把“内存怎么分配的”和“怎么调”彻底讲清楚。

Executor内存的完整分配

一个Executor的内存 = 堆内存(heap) + 堆外内存(overhead)。YARN分配给一个Container的内存 = executor-memory + memoryOverhead。

区域配置参数默认值用途
堆内存--executor-memory1gSpark执行引擎 + 用户代码
堆外内存spark.executor.memoryOverheadmax(384MB, 10%×heap)JVM堆外 + 网络缓冲区 + 序列化
PySpark额外spark.executor.pyspark.memory0Python进程内存

堆内存内部的分配(第1篇讲过):Reserved 300MB + User Memory + Unified Memory(Execution + Storage动态分配)。

三种“被杀”场景和解决方案

场景一:堆内存OOM(java.lang.OutOfMemoryError)

原因:Shuffle数据量超过Unified Memory,或用户代码创建了大量对象。

解决:加大executor-memory,或增加shuffle.partitions减少每个Task的数据量。

场景二:堆外内存超限(Container killed by YARN for exceeding memory limits)

原因:堆外内存默认只有10%,但某些操作(大量Shuffle、压缩/解压缩、Netty缓冲区)会占用大量堆外内存。这个问题很隐蔽,因为你加了executor-memory但堆外没加,问题依旧存在。

// 解决:加大堆外内存
spark.conf.set("spark.executor.memoryOverhead", "2g")  // 比默认10%大很多

场景三:GC停顿过久(ExecutorLostFailure: Executor heartbeat timed out)

原因:Java堆内存中对象太多,Full GC耗时太长,Executor心跳超时被Driver认为已死。

解决:减少cache的数据量、用MEMORY_AND_DISK代替MEMORY_ONLY、加大内存减少GC频率。

实战经验:我的调优检查清单

我在出海硬件公司建立的Spark作业调优检查清单:

1. 看Spark UI的Stage详情,找到最慢的Stage

2. 看该Stage的每个Task的Shuffle Read和Duration——认倾斜或OOM的具体Task

3. 看YARN日志确认是堆内OOM还是堆外超限还是GC超时

4. 对症下药:堆内OOM加executor-memory或加分区数,堆外超限加memoryOverhead,GC超时减少cache

总结

Executor被杀先看日志确认原因(堆内OOM/堆外超限/GC超时),再对症下药。不是所有问题都能用“加内存”解决。面试时能说清楚内存分配结构和三种场景的解决方案,就很有深度。

下一篇聊一个很实用的场景:Spark写出的小文件太多,下游Hive查询变慢了——怎么控制输出文件数?

你们遇到过堆外内存超限的问题吗?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器