之前是只知道内存模型理论上是怎么样的,这次拿到一个具体的任务,具体的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时的内存缓冲、占用空间比较大的记录做缓冲;估算大小为2.3G(8G-300M)*0.6*0.5
- execution:用于spark的计算:shuffle、sort、aggregation等这些计算时会用到的内存;估算大小为2.3G(8G-300M)*0.6*0.5
- storage:主要用于rdd的缓存;3G(8G-300M)*0.4
其中如果计算是内存execution不足会向storage部分借,如果还是不够就会spill到磁盘。
如果execution来借内存,storage会牺牲自己丢弃缓存来借给execution,storage也可以向execution借内存,但execution不会牺牲自己。
因此,我们可以认为计算内存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。
内存参数该设置多少,没有确切计算方法,可以依据经验设定,然后多次测试出最合适的值。