ITPub博客

首页 > 大数据 > > 从一个sql任务理解spark内存模型

从一个sql任务理解spark内存模型

作者:格伯纳 时间:2020-05-09 14:15:33 0 删除 编辑

之前是只知道内存模型理论上是怎么样的,这次拿到一个具体的任务,具体的executor来做对照分析,加深理解,在调内存参数时,也能有个依据。

1、背景

下面是一个sql任务的executor界面:

该任务运行没有报oom,能正够正常执行完毕,但观察executor Summary页面,有大量executor GC时间过长(GC时长已经超过总任务时长的10%%,一般GC时长建议控制在总任务时长的5%%以内)。

2、分析

先给出相关的参数(目前所在平台默认参数):

  • spark.executor.memory=8G
  • spark.executor.memoryOverhead=6144(6G)
  • spark.memory.fraction=0.6

观察每个stage及job,并没有数据倾斜现象。

以某个Executor为例:

Dtop mem:

从图上可以看到,used_mem为8.96G 左右(最大为9.85G),alloc_mem:14.34G。

会觉得:哎呀,内存看上去还可以呀,挺充足呀,怎么就GC了呢?

实际上dtop页面的:

alloc_mem=executor memory(8G)+ memoryOverhead(6G)

used_mem=jvm实际使用量+overhead实际使用量


overhead我们管不着,那看看jvm heap的情况吧。

monitor 的jvm相关:

主要看三个参数:

  • max_heap:表示可用的最大内存
  • commited_heap: JVM 堆已 commit 的内存(包括实际分配的物理内存和未实际分配的内存) commited_heap <= max_heap ,当used_heap 接近committed_heap的时候,heap就会grow up,直到等于max_heap
  • used_heap: jvm中活动对象占用的内存,即实际的物理使用内存

commited_heap已经为8G,达到最大极限了。

used_heap为5G左右,整个过程中,最大的能达到6.89G。

这时候,会不会又觉得,最大8G,现在最多也才用6.89G,还有1G的内存没用啊?


回顾一下spark统一内存模型:

jvm堆内的内存分为四个部分(spark.memory.fraction=0.6)

reservedMemory: 预留内存300M,用于保障spark正常运行

other memory: 用于spark内部的一些元数据、用户的数据结构、防止在稀疏和异常大的记录的情况下出现对内存估计不足导致oom时的内存缓冲;估算大小为3G(8G-300M)*0.4

execution: 用于spark的计算:shuffle、sort、aggregation等这些计算时会用到的内存; 估算大小为2.3G(8G-300M)*0.6*0.5

storage: 主要用于rdd的缓存;2.3G(8G-300M)*0.6*0.5


execution与storage 两个模块可以互相借用空间(动态占用机制),但有个前提,就是对方有足够的空间(默认情况下各占 50%%,由spark.memory.storageFraction参数决定,在这次任务中,各有2.3G的空间)


关于execution与storage 动态占用机制的理解(三种情况):

1、不使用缓存(storage)的应用程序可以将整个空间用于执行(execution),从而避免不必要的磁盘溢写。

2、storage 曾经向 execution 借了空间,它缓存的数据可能是非常的多,然后 execution 又不需要那么大的空间 ,假设现在 storage 占了 80%%,execution 占了 20%%,然后 execution 说自己空间不足,execution 会向内存管理器发信号把 Storgae 曾经占用的超过 50%数据的那部份强制挤掉 (注意:drop 后数据会不会丢失主要是看你在程序设置的 storage_level 来决定你是 Drop 到那里,可能 Drop 到磁盘上)(storage不足是指不足以放下一个完整的 Block)

3、execution 空间不足的情况下,除了选择向 storage 借空间以外,也可以把一部份数据 Spill 到磁盘上,但很多时候基于性能调优方面的考虑都不想把数据 Spill 到磁盘上,会优先选择向 storage 借空间。此时,正好storage有富裕空间,即storage实际占用不足 50%% 的情况下,storage 会借空间给execution 。 假设现在 storage 占了 20%%,execution 占了 80%%。 后面,storage发现自己空间不够用了,此时,只能等execution 释放空间,才能使用


因此,在这个案例中,有一些Broadcast的数据,但占比很小,可以认为计算内存execution 可用最大内存为4.6G

used_heap 包含了计算内存和 othermemory 、reservedmemory、storage 的真实使用量。

没办法看到othermemory部分的实际使用内存大小,但可以确定,富裕出来的1G左右的内存是othermemory 没有用完的并且计算内存execution一定是不太够用了,因为整个运行过程一直伴随着gc,并且gc的时间是越来越长:

最严重的是,在最后,老年代也发生了gc

3、总结

上面说了那么多,也有点乱,总结一下:

判断内存够不够,不能只看总图,要清楚内存分几个模块,几个模块分别起什么作用。一般出现内存不够用的地方是 shuffle时的计算内存,计算内存真实的可用内存大小并不是在dtop总图上看到的那么大。

如果spark.executor.memory=8G , 则计算内存可用最大为:4.6G

从上面分析,发现堆外内存堆最大使用量差不多2G,而默认的 spark.executor.memoryOverhead=6144(6G) 有点浪费

最后测试参数:

spark.executor.memory=12G

spark.executor.memoryOverhead=3072(3G)

set spark.memory.fraction=0.75

最合适


其中spark.memory.fraction 不能设置太高,测试时,要为othermemory留一些富裕内存,因为spark内存统计信息收集是有延迟的,如果该值过大,且spill较重情况下,会导致内存释放不及时而OOM。

内存参数该设置多少,没有确切计算方法,可以依据经验设定,然后多次测试出最合适的值。

是不是要绕晕了?

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31509949/viewspace-3118311/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
管理员

注册时间:2018-03-30

  • 博文量
    2383
  • 访问量
    983936