大家好,又见面了,我是你们的朋友全栈君。
本文首发于 www.yidooo.net/2018/07/29/… 转载请注明出处
引言
Spark 作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统中扮演着非常重要的角色。理解 Spark 内存管理的基本原理,有助于更好地开发 Spark 应用程序和进行性能调优。
一个Spark Application一般包括Driver和Executor两种JVM进程。Driver为主控进程,负责创建Context,提交Job,并将Job转换为Task,协调Executor间的Task执行。而Executor主要负责执行具体的计算任务,将结果返回Driver。 由于Driver的内存管理比较简单,和一般的JVM程序区别不大,所以本文重点分析Executor的内存管理。所以,本文提到的内存管理都是指Executor的内存管理。
堆内内存和堆外内存
Executor作为一个JVM进程,它的内存管理是基于JVM之上的。所以JVM的内存管理包括两种方式:
- 堆内内存管理(On-Heap):对象分配的在JVM的堆上,对象会受GC束缚。
- 堆外内存管理(Off-Heap):对象通过序列化分配在JVM之外的内存里,由应用程序对其进行管理,且不受GC束缚。这种内存管理方式可以避免频繁的 GC,但缺点是必须自己编写内存申请和释放的逻辑。
一般来说对象读写速度是:on-heap > off-heap > disk
内存空间分配
在Spark中,支持两种内存管理方式:静态内存管理(Static Memory Manager)和统一内存管理(Unified Memory Manager)。
Spark为Storage内存和Execution内存的管理提供了统一的接口MemoryManager,同一个 Executor内的任务都调用这个接口的方法来申请或释放内存。MemoryManager的实现上,Spark 1.6以前默认采用的是静态内存管理([StaticMemoryManager]((github.com/apache/spar…))的方式;而在Spark1.6以后,默认采用的是统一内存管理(UnifiedMemoryManager)的方式。在中Spark 1.6+中,可以通过spark.memory.useLegacyMode参数启用静态内存管理。
静态内存管理(Static Memory Manager)
静态内存管理机制下,Storage内存、Execution内存和其他内存的大小在 Spark 应用程序运行期间均为固定的,但用户可以应用程序启动前进行配置。由于这种分配已经逐渐被淘汰,但出于兼容性考虑,Spark依然保留下来。有兴趣的话,可以参考:blog.csdn.net/Lin_wj1995/…
这边主要讲下静态内存管理的弊端:静态内存管理机制实现起来较为简单,但如果用户不熟悉Spark的存储机制,或没有根据具体的数据规模和计算任务或做相应的配置,很容易造成Storage内存和Execution内存中的一方剩余大量的空间,而另一方却早早被占满,不得不淘汰或移出旧的内容以存储新的内容。
统一内存管理(Unified Memory Manager)
Spark 1.6之后引入了统一内存管理机制,该机制与静态内存管理的区别在于,Storage内存和Execution内存是共享一块内存空间的,双方可以互相占用对方的空闲区域。
堆内模型
默认情况下,Spark仅使用了堆内内存。堆内内存的大小由Spark Application启动时的–executor-memory或spark.executor.memory 参数配置。Executor内运行的并发任务共享JVM堆内内存。
Executor端的堆内内存区域大致可以分为以下四大块:
- Storage内存(Storage Memory):主要用于存储Spark的cache数据,例如RDD的缓存、Broadcast变量,Unroll数据等。
- Execution内存(Execution Memory):主要用于存放 Shuffle、Join、Sort、Aggregation等计算过程中的临时数据。
- 用户内存(User Memory):主要用于存储 RDD 转换操作所需要的数据,例如 RDD 依赖等信息。
- 预留内存(Reserved Memory):系统预留内存,会用来存储Spark内部对象。
内存分布如下图所示:
堆外模型
Spark 1.6 开始引入了Off-heap memory(SPARK-11389)。默认情况下,堆外内存是关闭的,我们可以通过spark.memory.offHeap.enabled参数启用,通过spark.memory.offHeap.size设置堆外内存大小。相比堆内内存,堆外内存的模型比较简单,只包括Storage内存和Execution内存,其分布如下图所示:
如果堆外内存被启用,那么Executor内将同时存在堆内和堆外内存,两者的使用互补影响,这个时候Executor中的Execution内存是堆内的Execution 内存和堆外的Execution内存之和,同理,Storage内存也一样。下图为Spark堆内和堆外示意图
动态占用机制
- 在程序提交时,会根据spark.memory.storageFraction参数设置Storage内存区域和Execution内存区域。
- 在程序运行时,如果双方的空间不不足(存储空间不足以放下一个完整的Block),则按照LRU规则存储到磁盘;如果己方空间不足而对方空间有空余,则借用对方的空间。
- Storage占用对方内存,可将占用的部分转存到硬盘,然后”归还”借用的空间。
- Execution占用对方内存,目前的实现是无法让对方”归还”的。因为Shuffle过程产生的文件在后面一定会被使用到,而Cache在内存的数据不一定在后面使用,归还内存可能会导致性能严重下降。
参考资料
转载于:https://juejin.im/post/5b94937e6fb9a05cd456c337
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/107325.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...