在YARN上运行Spark时,内存管理是性能调优的核心环节。以下是 Driver Memory、Executor Memory、堆内存(Heap Memory) 和 堆外内存(Off-Heap Memory) 的区别与配置方法,以及实际场景中的最佳实践:
1. 核心概念与区别
(1) Driver Memory
- 角色:Driver是Spark应用程序的主控进程,负责:
- 解析用户代码,生成DAG(任务执行计划)。
- 调度Task到Executor,并监控执行状态。
- 收集结果(如
collect()
操作)或广播变量。
- 内存组成:
- 堆内存:存储元数据(如Task定义、广播变量)和收集的结果数据。
- 堆外内存:JVM自身开销、直接内存(如网络传输缓存)。
- 关键参数:
spark.driver.memory
:Driver的堆内存(默认1g
)。spark.driver.memoryOverhead
:Driver的堆外内存(默认max(384MB, 0.1 * spark.driver.memory)
)。
(2) Executor Memory
- 角色:Executor是工作节点上的任务执行进程,负责:
- 执行具体的Task(如Map、Reduce操作)。
- 缓存数据(如
cache()
、persist()
)。
- 内存组成:
- 堆内存:存储Task处理的数据、缓存的数据。
- 堆外内存:Shuffle中间数据、原生操作(如HDFS读写缓存)。
- 关键参数:
spark.executor.memory
:Executor的堆内存(默认1g
)。spark.executor.memoryOverhead
:Executor的堆外内存(默认max(384MB, 0.1 * spark.executor.memory)
)。spark.memory.fraction
:Executor中用于计算和缓存的总内存占比(默认0.6
)。
(3) 堆内存 vs 堆外内存
特性 | 堆内存(Heap) | 堆外内存(Off-Heap) |
---|---|---|
管理方式 | 由JVM垃圾回收器(GC)管理 | 不受GC管理,需手动释放或依赖操作系统管理 |
存储内容 | Java对象实例(如RDD数据、集合) | JVM元数据、直接缓冲区(DirectByteBuffer )、Shuffle临时文件 |
溢出风险 | OutOfMemoryError: Java heap space |
OutOfMemoryError: Direct buffer memory 或YARN/K8s容器被杀死 |
配置参数 | spark.driver.memory 、spark.executor.memory |
spark.driver.memoryOverhead 、spark.executor.memoryOverhead |
2. 内存设置规则
(1) 总内存限制
在YARN集群中,Driver和Executor的内存需满足:
- Driver总内存 ≤ YARN单容器内存上限(由
yarn.scheduler.maximum-allocation-mb
定义)。 - Executor总内存 ≤ YARN单容器内存上限。
总内存计算公式:
Driver总内存 = spark.driver.memory + spark.driver.memoryOverhead
Executor总内存 = spark.executor.memory + spark.executor.memoryOverhead
(2) 配置建议
参数 | Driver | Executor | 说明 |
---|---|---|---|
堆内存 | spark.driver.memory |
spark.executor.memory |
- Driver:根据收集数据量和广播变量大小调整。 - Executor:根据分区数据量和缓存需求调整。 |
堆外内存 | spark.driver.memoryOverhead |
spark.executor.memoryOverhead |
- 默认值通常不足!若任务涉及大量Shuffle或Native操作,需手动增加。 |
内存分配比例 | - | spark.memory.fraction |
调整Executor内计算内存(Execution)和缓存内存(Storage)的比例。 |
(3) 典型场景设置示例
场景1:常规ETL任务
spark-submit \ --driver-memory 4g \ --executor-memory 8g \ --conf spark.executor.memoryOverhead=2g \ --conf spark.driver.memoryOverhead=1g \ ...
- 说明:Executor处理数据分区,预留2GB堆外内存应对Shuffle。
场景2:需
collect()
大量数据spark-submit \ --driver-memory 16g \ # 收集10GB数据时,Driver堆内存需足够大 --conf spark.driver.maxResultSize=10g \ ...
- 注意:避免
collect()
,优先使用分布式写入(如write.parquet()
)。
- 注意:避免
场景3:机器学习(频繁Shuffle)
spark-submit \ --executor-memory 16g \ --conf spark.executor.memoryOverhead=4g \ # Shuffle和原生库可能占用大量堆外内存 --conf spark.memory.fraction=0.7 \ # 提高计算内存占比 ...
3. 常见问题与调优
(1) Driver OOM(堆内存不足)
- 表现:
java.lang.OutOfMemoryError: Java heap space
。 - 解决方案:
- 增加
spark.driver.memory
。 - 避免在Driver中收集大数据(用
take(n)
替代collect()
)。 - 减少广播变量大小。
- 增加
(2) Executor OOM(堆内存不足)
- 表现:Executor日志中抛出堆内存溢出。
- 解决方案:
- 增加
spark.executor.memory
。 - 减少单个分区的数据量(通过
repartition()
增大分区数)。 - 使用
MEMORY_AND_DISK
缓存级别。
- 增加
(3) 容器被YARN杀死(堆外内存不足)
- 表现:YARN日志提示
Container killed due to exceeding memory limits
。 - 解决方案:
- 增大
spark.executor.memoryOverhead
或spark.driver.memoryOverhead
。 - 检查是否使用Native库(如OpenBLAS)导致堆外内存泄漏。
- 增大
(4) Shuffle阶段频繁溢写磁盘
- 表现:Spark UI中Shuffle Write/Read量过大,任务变慢。
- 解决方案:
- 增大
spark.executor.memory
和spark.memory.fraction
(为Execution内存留更多空间)。 - 优化数据倾斜(加盐、拆分倾斜Key)。
- 增大
4. 最佳实践总结
配置项 | 推荐策略 |
---|---|
Driver堆内存 | 根据collect() 数据量设置,通常为数据量的2倍,不超过YARN容器上限。 |
Executor堆内存 | 根据分区数据量设置,建议8g~16g,避免单个Executor内存过大导致GC停顿。 |
堆外内存 | 至少为堆内存的20%~30%(如spark.executor.memoryOverhead=4g 当Executor堆内存为16g)。 |
Shuffle优化 | 增大spark.sql.shuffle.partitions (默认200)到2-3倍数据分区数。 |
监控工具 | 使用Spark UI + YARN Web UI + Prometheus监控堆/堆外内存趋势。 |
5. 完整示例配置
spark-submit \
--master yarn \
--deploy-mode cluster \
--driver-memory 8g \
--executor-memory 16g \
--conf spark.driver.memoryOverhead=2g \
--conf spark.executor.memoryOverhead=4g \
--conf spark.memory.fraction=0.7 \
--conf spark.sql.shuffle.partitions=2000 \
--conf spark.serializer=org.apache.spark.serializer.KryoSerializer \
--class com.example.Main \
/path/to/your-app.jar
通过合理分配堆内外内存、监控资源使用,并结合业务逻辑优化,可以显著减少Spark作业的OOM风险和性能瓶颈。