图1 单Executor节点内存布局
我们可以看到在Yarn集群管理模式中,Spark 以 Executor Container 的形式在 NodeManager 中运行,其可使用的内存上限由 yarn.scheduler.maximum-allocation-mb 指定,我们称之为 MonitorMemory。
整个Executor内存地区分为两块:
1、JVM堆外内存
巨细由 spark.yarn.executor.memoryOverhead 参数指定。默认巨细为 executorMemory * 0.10, 最小384m。
此部分内存重要用于JVM自身,字符串, NIO Buffer(Driect Buffer)等开销。此部分为用户代码及Spark 不可操作的内存,不敷时可通过调整参数办理。
The amount of off-heap memory (in megabytes) to be allocated per executor. This is memory that accounts for things like VM overheads, interned strings, other native overheads, etc. This tends to grow with the executor size (typically 6-10%).
2、堆内内存(ExecutorMemory)
巨细由 Spark 应用程序启动时的 –executor-memory 或 spark.executor.memory 参数设置,即JVM最大分配的堆内存 (-Xmx)。Spark为了更高效的使用这部分内存,对这部分内存进行了逻辑上的划分管理。我们在下面的同一内存管理会详细先容。
【注意】对于Yarn集群,存在: ExecutorMemory + MemoryOverhead <= MonitorMemory,若应用提交之时,指定的 ExecutorMemory 与 MemoryOverhead 之和大于 MonitorMemory,则会导致 Executor 申请失败;若运行过程中,实际使用内存超过上限阈值,Executor 进程会被 Yarn 停止掉 (kill)。
同一内存管理
图5 同一内存管理机制
其中最紧张的优化在于动态占用机制,其规则如下:
1、程序提交的时候我们都会设定基本的 Execution 内存和 Storage 内存地区(通过 spark.memory.storageFraction 参数设置)。我们用 onHeapStorageRegionSize 来表现 spark.storage.storageFraction 划分的存储内存地区。这部分内存是不可以被驱逐(Evict)的存储内存(但是如果空闲是可以被占用的)。
2、当盘算内存不敷时,可以借用 onHeapStorageRegionSize 中未使用部分,且 Storage 内存的空间被对方占用后,需要期待实行内存自己开释,不能抢占。
3、若实际 StorageMemory 使用量超过 onHeapStorageRegionSize,那么当盘算内存不敷时,可以驱逐并借用 StorageMemory – onHeapStorageRegionSize 部分,而 onHeapStorageRegionSize 部分不可被抢占。
4、反之,当存储内存不敷时(存储空间不敷是指不敷以放下一个完整的 Block),也可以借用盘算内存空间;但是 Execution 内存的空间被存储内存占用后,是可让对方将占用的部分转存到硬盘,然后“归还”借用的空间。
5、如果双方的空间都不敷时,则存储到硬盘;将内存中的块存储到磁盘的计谋是按照 LRU 规则进行的。 阐明
1、出于兼容旧版本的应用程序的目的,Spark 仍然保存了它的实现。可通过设置 spark.memory.useLegacyMode 参数启用。
2、spark.memory.storageFraction 是不可被驱逐的内存空间。只有空闲的时候能够被实行内存占用,但是不能被驱逐抢占。
Amount of storage memory immune to eviction, expressed as a fraction of the size of the region set aside by spark.memory.fraction. The higher this is, the less working memory may be available to execution and tasks may spill to disk more often. Leaving this at the default value is recommended. For more detail, see this description.
3、Storage 内存的空间被对方占用后,目前的实现是无法让对方”归还”,由于需要考虑 Shuffle 过程中的很多因素,实现起来较为复杂;而且 Shuffle 过程产生的文件在反面一定会被使用到,而 Cache 在内存的数据不一定在反面使用。在 Unified Memory Management in Spark 1.6 中详细讲解了为何选择这种计谋,简朴总结如下:
3.1、数据清除的开销 : 驱逐storage内存的开销取决于 storage level,MEMORY_ONLY 可能是最昂贵的,由于需要重新盘算,MEMORY_AND_DISK_SER 恰恰相反,只涉及到磁盘IO。溢写 execution 内存到磁盘的开销并不昂贵,由于 execution 存储的数据格式紧凑(compact format),序列化开销低。并且,清除的 storage 内存可能不会被用到,但是,可以预见的是,驱逐的 execution 内存是必然会再被读到内存的,频仍的驱除重读 execution 内存将导致昂贵的开销。
3.2、实现的复杂度 : storage 内存的驱逐是容易实现的,只需要使用已有的方法,drop 掉 block。execution 则复杂的多,首先,execution 以 page 为单位管理这部分内存,并且确保相应的操作至少有 one page ,如果把这 one page 内存驱逐了,对应的操作就会处于饥饿状态。别的,还需要考虑 execution 内存被驱逐的环境下,期待 cache 的 block 如那里理。
4、上面说的借用对方的内存需要借用方和被借用方的内存范例都一样,都是堆内内存或者都是堆外内存,不存在堆内内存不敷去借用堆外内存的空间。
任务内存管理(Task Memory Manager)