工作中经常用到Spark内存调参,之前还没对这块记录,这次记录一下。
环境参数
spark 内存模型中会涉及到多个配置,这些配置由一些环境参数及其配置值有关,为防止后面理解混乱,现在这里列举出来,如果忘记了,可以返回来看看:
- spark.executor.memory:JVM On-Heap 内存(堆内内存),在使用 spark submit 提交的时候,可以通过配置 --executor-memory 来对这个值进行修改。
- spark.yarn.executor.memoryOverhead:这是用于配置 Executor 的额外内存,因为 Executor 在执行的时候,可能会超过 executor memory,所以会为 executor 预留一部分内存。
- spark.memory.offHeap.enabled :用于开启堆外内存(PS:这个是系统级别的,不受 JVM 管理)。
- spark.memory.offHeap.size : 设置堆外内存大小;
- spark.memeory.fraction :用于配置统一内存,这个值在 Spark 2.0+ 为 60%,Spark 1.6 为 75%。
- spark.storage.storageFraction :用于从统一内存中分配 Storage Memory 的比例。
- yarn.scheduler.maximum-allocation-mb :Spark 在 Worker 节点的可用内存。
- spark.executor.cores:程序需要使用到的核数。
Executor 内存划分
由 yarn.scheduler.maximum-allocation-mb 指定 NodeManager 上 JVM 的内存,提交任务时,如果 MemoryOverhead 和 Executor Memory 所占的内存之和大于分配的内存之和,那就会造成 Executor 提交失败;运行过程中超过上限阈值,进程会被杀掉。
堆内内存(On-Heap Memory)
逐一介绍各个 Memory:
- Executor Memory: 由 spark.executor.memory 配置,或者在提交的时候使用 --executor-memory 进行配置。
- Reserved Memory: 这个内存是写死了的,默认 300MB,但也可以修改,前提是在测试环境下,通过修改 spark.test.reservedMemory 参数对这个值进行修改;这块内存用于存储 Spark 内部的对象。
- Usable Memory: Executor Memory - Reserved Memory 就是可用内存。
- Unified Memory: Usable Memory * spark.memeory.fraction 比例值(约等于 Usable Memory * 60%),这个内存由 Storage 和 Execution 共用,这两个之间有一个动态调节机制,后面说。
- Storage Memory: Unified Memory * spark.storage.storageFraction 比例值(约等于 Unified Memory * 50%),这块内存主要是用来存储一些缓存数据的,比如 cache(),persist(),RDD 的缓存数据等。
- Execution Memory: Unified Memory * (1 - spark.storage.storageFraction 比例值),这块内存用于存储 Shuffle,Join,Sort,Aggregate 等计算过程中的临时数据。
- User Memory : Usable Memory * (1 - spark.memeory.fraction 比例值),这块内存用于保存 RDD 转换操作时需要的一些数据,如父子 RDD 的依赖关系。
堆外内存(Off-Heap Memory)
这里要介绍的内存只有一个 Off-Heap Memory:
堆外内存是 Spark 1.6+ 以后引入的一种新的内存,Spark 可以直接操作系统的堆外内存,减少了不必要的内存开销,比如 GC 扫描和垃圾回收,但也正因为堆外内存不再由 JVM 管理,所以需要手动实现内存的申请和释放逻辑,提高了内存操作的精度。
堆外内存的大小可以通过spark.memory.offHeap.size 参数进行配置,但是堆外内存是默认关闭的,可以通过配置 spark.memory.offHeap.enable 参数进行开启。
动态调节机制
Spark 1.5 以前,Storage Memory 和 Execution Memory 的大小分配是静态的(也就是说从一开始计算好大小后就不会变了),当两块内存满了以后,就会把溢出的数据落到磁盘上,但总所周知,从磁盘读取数据是没有从内存中读取数据快的,所以在后来加上了动态调节机制:
- Spark 程序提交后会计算 Storage Memory 和 Execution Memory 的内存大小并进行分配;
- 当两个内存空间都不足后,就会下落到磁盘上;若对方空间富余,就会向另一端借空间:
- Storage 向 Execution 借空间后,Execution 可以主动向 Storage 申请归还空间,并让 Storage 将数据放到磁盘上;
- Execution 向 Storage 借空间后,Storage 是无法主动让 Execution 归还空间的,因为 Execution 中存在 Shuffle 数据,该数据需要在网络中频繁传输,随时都会用到,而 Storage 中缓存的数据相对于 Shuffle 数据更会更少用到。
Task 能申请到的内存
spark.executor.cores 参数值就是 Spark 程序运行时得到的核数(以下简称为 N),每个 Task 能够分配到的内存大小为 1/2N ~ 1/N(举例,N=4,分配到的内存为 10G,那内存大小为 1.25G ~ 2.5G)。