Spark 性能调优实战
吴磊
前 FreeWheel 机器学习团队负责人
8808 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 36 讲
Spark 性能调优实战
15
15
1.0x
00:00/00:00
登录|注册

07 | 内存管理基础:Spark如何高效利用有限的内存空间?

你好,我是吴磊。
对于 Spark 这样的内存计算引擎来说,内存的管理与利用至关重要。业务应用只有充分利用内存,才能让执行性能达到最优。
那么,你知道 Spark 是如何使用内存的吗?不同的内存区域之间的关系是什么,它们又是如何划分的?今天这一讲,我就结合一个有趣的小故事,来和你深入探讨一下 Spark 内存管理的基础知识。

内存的管理模式

在管理方式上,Spark 会区分堆内内存(On-heap Memory)和堆外内存(Off-heap Memory)。这里的“堆”指的是 JVM Heap,因此堆内内存实际上就是 Executor JVM 的堆内存;堆外内存指的是通过 Java Unsafe API,像 C++ 那样直接从操作系统中申请和释放内存空间。
其中,堆内内存的申请与释放统一由 JVM 代劳。比如说,Spark 需要内存来实例化对象,JVM 负责从堆内分配空间并创建对象,然后把对象的引用返回,最后由 Spark 保存引用,同时记录内存消耗。反过来也是一样,Spark 申请删除对象会同时记录可用内存,JVM 负责把这样的对象标记为“待删除”,然后再通过垃圾回收(Garbage Collection,GC)机制将对象清除并真正释放内存。
JVM堆内内存的申请与释放
在这样的管理模式下,Spark 对内存的释放是有延迟的,因此,当 Spark 尝试估算当前可用内存时,很有可能会高估堆内的可用内存空间。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文介绍了Spark内存管理的基础知识,包括堆内内存和堆外内存的管理模式,以及内存区域的划分。堆内内存由JVM管理,而堆外内存则通过Unsafe API直接从操作系统中申请和释放内存空间。内存区域划分为Execution Memory、Storage Memory、User Memory和Reserved Memory,它们之间存在抢占规则。文章还通过一个地主招租的故事形象地解释了Execution Memory和Storage Memory之间的抢占规则。此外,文章还通过一个代码示例分析了不同代码对不同内存区域的消耗。总的来说,本文深入浅出地介绍了Spark内存管理的基础知识,有助于读者了解内存管理的机制,提升应用的执行性能。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Spark 性能调优实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(38)

  • 最新
  • 精选
  • -.-
    受益匪浅,开始看第二遍了!有个问题想请教下,spark.executor.memoryOverhead控制的是堆外内存的大小,官方文档解释:This is memory that accounts for things like VM overheads, interned strings, other native overheads, etc.1. 如果设置spark.memory.offHeap.enabled=false,这块内存是不是只是jvm的堆外内存而不是spark管理的堆外内存,不会被用于执行内存和缓存内存? 2. 如果设置spark.memory.offHeap.enabled=true,这块内存中是不是会包含offHeapSize,其中一部分为JVM堆外内存一部分为offHeap的执行内存和缓存内存?

    作者回复: 先来说说这个spark.executor.memoryOverhead 哈,在yarn、k8s部署模式下,container会预留一部分内存,形式是堆外,用来保证稳定性,主要存储nio buffer,函数栈等一些开销,所以你看名字:over head。这部分内存,不管堆外还是堆内,开发者用不到,spark也用不到,所以不用关心,千万不指望调这个参数去提升性能,它的目的是保持运行时的稳定性~ 想利用堆外让spark去管理数据、加速执行效率,只有off heap那两个参数,一个用来enable(spark.memory.offHeap.enabled=true)、一个指定大小(spark.memory.offHeap.size)。这两个才是正儿八经的off heap key configs。 回答你的问题: 1. 是的,没错,它不是Spark管理的内存空间,不会用到Execution或是Storage 2. 不会,这部分overhead是单独划分的,它不会参与到Spark诸多内存空间的计算,是完全独立的一块区域,也就是前面说的container预留的“buffer”。所以完全不用理它,它的目的是提供稳定性,不参与Spark任务计算。

    2021-05-12
    5
    19
  • 斯盖丸
    堆内内存中:保留内存300M,用户内存为20*0.2=4GB,Storage内存为20*0.8*0.6=9.6GB,Execution内存为20*0.8*0.4=6.4GB 堆外内存中:Storage内存为10*0.6=6G,Execution内存为10*0.4=4G

    作者回复: 满分💯

    2021-03-29
    7
    9
  • Kendrick
    有点疑惑,我想知道堆外内存存在的意义是什么,有什么场景是一定需要堆外内存么?

    作者回复: 好问题,其实spark官方建议谨慎使用堆外内存,为啥呢? 原因其实很简单,在于堆外堆内的空间互不share,也就是说,你的task最开始用堆外,用着用着发现不够了,这个时候即使堆内还有空闲,task也没法用,所以照样会oom。 内存本来就有限,再强行划分出两块隔离的区域,其实反而增加了管理难度。tungsten在堆内其实也用内存页管理内存(Tungsten的相关优化,可以参考后面Tungsten那一讲),也用压缩的二进制数据结构,因此gc效率往往可以保障,这也是为什么官方推荐就用堆内就可以了。 回答你的问题,我不觉得有什么场景一定要用堆外,就我看来,对于开发者来说,堆外更多地是一种备选项,是Optional的。不过,尽管如此,我们还是要知道堆外、堆内各自有哪些优缺点、优劣势,这样在结合应用场景做选择的时候,也能有的放矢~

    2021-05-25
    2
    8
  • 井先生
    试读了几节果断订阅了。 开启堆外内存后,分配的内存空间是多大?这时候还会分配堆内内存吗?谢谢

    作者回复: 具体大小可以通过参数来配置哈,堆内也一样,都是用参数开调控。不过需要注意,堆内、堆外的内存,互相之间不共享。也就是一开始你的task用off heap,后来用着用着发现不够了,这个时候是不能去占有堆内内存的,所以即便堆内有空闲,也还是会oom。所以在划分堆内堆外之前,要提前计划好,如果怕麻烦,就都用堆内。tungsten对于堆内的内存管理做的也很好,大多数场景都问题不大~

    2021-03-31
    7
  • LYL
    老师,有几个问题我不太明白, 1.tungsten中的page用于同一管理off-heap和on-heap,利用这个机制可否在spark runtime的时候shuffle同时使用堆内和堆外内存? 2.在cache rdd的时候是否能指定StorageLevel为off_heap在spark runtime时使用堆外内存,memory_only的情况下使用堆内内存,或者说在配置开启堆外内存的参数之后,所有内存都是走堆外内存,无法使用堆内内存

    作者回复: 1. Tungsten确实统一了内存管理,使用Page来管理内存,这样做得目的,主要在于统一内存对象(内存页)抽象。对于堆内来说,内存页本质上就是个大对象,没什么新鲜的;但对于堆外来说,那可正儿八经的是OS的内存寻址。因此,两块内存不能“同时”使用。换句话说,一个任务,不管是执行任务、还是缓存任务,你要么用堆外,要么用堆内,驴和熊猫不可兼得,不能脚踏两条船。 2. 开启堆外之后,执行任务默认会走堆外,堆外用尽了,后续的任务才会走堆内。对于缓存来说,如果你明确指定了用off heap,那就是明确走堆外,如果你不明确指定,那么默认走堆内。

    2021-04-18
    6
  • 苏子浩
    老师,您好!我想问一下在文中提到“reduceByKey算子会引入 Shuffle,而 Shuffle 过程中所涉及的内部数据结构,如映射、排序、聚合等操作所仰仗的 Buffer、Array 和 HashMap,都会消耗 Execution Memory 区域中的内存。”上一节说到Shuffle的中间结果会写入磁盘:Shuffle manager通过BlockManager调用DiskStore的putBytes()方法将数据块写入文件。这里的联系是什么呢?在内存和磁盘上有点不理解,不好意思,感谢解答!

    作者回复: “reduceByKey算子会引入 Shuffle,而 Shuffle 过程中所涉及的内部数据结构,如映射、排序、聚合等操作所仰仗的 Buffer、Array 和 HashMap,都会消耗 Execution Memory 区域中的内存。” 内存:上面说的这些操作,都会消耗内存空间,不过Map阶段的每一个计算环节,都是为了生成中间文件(data和index文件); 磁盘:在生成中间文件的时候,就会涉及磁盘、涉及diskStore的putByes写文件。比如临时文件溢出、比如merge得到的中间文件,等等。 或者更简单地,Shuffle过程中,只有写临时文件、和Shuffle中间文件,才会涉及diskStore和相关的磁盘操作。其他的计算步骤,都是在内存中完成的,会消耗如上的数据结构。

    2021-04-15
    2
    5
  • 赌神很低调
    老师好,有几个问题不是很明白想问下: 1、spark中内存划分是逻辑上的,真正的管理还是在jvm。如user memory占用内存超过设定值,还是会占用框架内存。但框架内存会根据设定值让task做一些阻塞或spill操作,所以从这个层面上说,框架内存的值得正确设置,如用户不会用到大的list、map等内存集合,就要把用户内存空间设置得够小,以保证框架内存(执行内存+存储内存)足够大,避免不必要的阻塞或spill操作? 2、如果开启了堆外内存,即使堆外内存不够,堆内内存充足,task也只会用堆外内存而不会用堆内内存? 3、spark 2.x版本中如果开启了堆外内存,并设置了spark.memory.offHeap.size=500mb,在yarn上跑的话spark.executor.memoryOverhead除了默认需要的10%是否还有要加上这500mb,否则container不会分配堆外这500mb的内存?看网上说3.0以上就不用加了。 4、task会在哪些场景申请和释放内存呢?只是shuffle的场景吗?transformer场景会吗?

    编辑回复: 1、spark中内存划分是逻辑上的,真正的管理还是在jvm。如user memory占用内存超过设定值,还是会占用框架内存。但框架内存会根据设定值让task做一些阻塞或spill操作,所以从这个层面上说,框架内存的值得正确设置,如用户不会用到大的list、map等内存集合,就要把用户内存空间设置得够小,以保证框架内存(执行内存+存储内存)足够大,避免不必要的阻塞或spill操作? 回答:是的,Spark的内存管理,更多的是一种”审计“上的管理,底下有JVM,Spark就不可能直接管理内存。通过内存管理机制,Spark更多地是设置一些软限制,从而从应用层面来将内存划分为不同区域,这些区域,在JVM看来,是没有区别的。如你所说:”如用户不会用到大的list、map等内存集合,就要把用户内存空间设置得够小,以保证框架内存(执行内存+存储内存)足够大,避免不必要的阻塞或spill操作“,确实是这样的。 2、如果开启了堆外内存,即使堆外内存不够,堆内内存充足,task也只会用堆外内存而不会用堆内内存? 回答:内存是用堆外,还是堆内,是以Job为粒度的,也就是说,要设置堆外内存,我们得确保堆外大小足以应对当前的作业,作业里面所有的tasks,都只能用堆外(如果作业在内存设置上用了堆外)。那么显然,此时跑在堆外的Job,假设内存不够用了,即便堆内还有剩余,也不会给这个Job用,这个Job还是会抛OOM。 3、spark 2.x版本中如果开启了堆外内存,并设置了spark.memory.offHeap.size=500mb,在yarn上跑的话spark.executor.memoryOverhead除了默认需要的10%是否还有要加上这500mb,否则container不会分配堆外这500mb的内存?看网上说3.0以上就不用加了。 回答:对的,堆外就是JVM heap以外的内存,以前的话,yarn把这部分内存算在container里面,现在不算在container里面了,不过这样其实有风险,因为堆外内存大小,对于yarn来说透明了,如果在运行时,Spark Job跑着跑着,发现OS根本分配不了500mb,那这个事情yarn是不负责的。 4、task会在哪些场景申请和释放内存呢?只是shuffle的场景吗?transformer场景会吗? 回答:凡是利用到AppendOnlyMap,PartitionPairBuffer这两个数据结构的计算,都要申请、释放内存,跟算子没啥关系哈,主要是shuffle write阶段的计算。

    2022-03-29
    3
  • Sean
    从第一章看到了第十一章,在留言去里面学习到了很多,老师对知识的传授也很有技巧,个人也是受益匪浅,随着阅读的慢慢深入的,总结了一些自己理解和疑惑,现在又回到了第七章,总结了一些问题,希望老师可以帮忙解惑,感谢! 1.在缓存rdd时,既然executor memory 和 storage memory 两块内存不可互相share,那是不是可以通过persist来指定呢,一部分rdd使用execm 一部分rdd使用storm呢? 2.只要不开启off heap,spark就无法使用off heap,包括yarn,k8s模式利用off heap提升稳定性也无法体现出来,一旦开启了off heap,执行任务也就是executor memory优先使用off heap,storage memory还是优先堆内内存,可以这样理解吗? 3.例如:spark executor如果配置了堆内和堆外各4GB,executor cores配置为2。那么该executor运行的第一个task只会使用堆外内存?调度来的第二个task,哪怕堆外剩余几十MB,它也会用堆外内存,如果第二个task发现堆外不够用,就会写磁盘,或清除部分堆外内存数据呢 4.shuffle 阶段的稳定性参数 spark.excludeOnFailure.application.fetchFailure.enabled 从官网描述上来看,这个参数对fetch failed会切换到别的节点,结合实际情况,在Map 阶段:Shuffle writer 按照 Reducer 的分区规则将中间数据写入本地磁盘过程中,刚好写人的datanode 的数据卷故障,但是并没有触发重试机制,而是一直runing状态,是不是可以通过启用application.fetchFailure.enabled来识别,目前使用的是物理机,这种情况也是偶尔发生一次,所以很难验证

    作者回复: 1. persist只能用来指定存储模式,memory还是disk,但不管什么缓存,都只能消耗Storage Memory 2. 对的,off heap必须显示开启才行。一旦开启off heap,作业会优先用off heap 3. 这个比较难,off heap、on heap,是以作业为控制粒度的,不是以task为控制粒度,也就是说,一个作业,要么都用off heap,要么都用on heap,不存在一个作业内部不同task,有的用堆外、有的用堆内。这个实现机制其实是有优化空间的~ 4. 这个参数,我理解是用来blacklist executors用的,也就是当一些executors频繁失败,spark会把他们标记到blacklist黑名单,避免下次DAGScheduler把任务调度到标记到blacklist的executors上面去。

    2021-08-24
    3
  • 西南偏北
    第一题: 缓存rdd:rdd.persist(StorageLevel.OFF_HEAP) 第二题: 因为堆内内存的申请和释放是由JVM来统一管理,对Spark来说是不那么透明可控的;而堆外内存需要调用Unsafe的allocateMemory和freeMemory方法来进行内存的申请和释放,完全由Spark来控制,所以估算会相对更精准。 第三题: - Reserved:300M - User:(20GB - 300MB) * (1 - 0.8) - Execution:(20GB - 300MB) * 0.8 * (1 - 0.6) + 10GB * (1 - 0.6) - Storage:(20GB - 300MB) * 0.8 * 0.6 + 10GB * 0.6

    作者回复: 第二、三题完美~ 💯 第一题答得也不错~ 等到看完Shuffle那讲,可以再回过头来想想,都有哪些数据结构,可以利用到堆外内存~

    2021-05-03
    2
    3
  • Z宇锤锤
    启用off-heap以后,RDD可以直接缓存到off-heap上。

    作者回复: 是的,不过要显示地(Explicitly)指定存储级别:OFF_HEAP rdd.persist(OFF_HEAP)

    2021-04-26
    3
收起评论
显示
设置
留言
38
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部