出现OMM的现象
参考:https://blog.csdn.net/zhuiqiuuuu/article/details/86539385
出现这种情况的大约有两个情况:
1、map执行内存溢出
2、shuffle后内存溢出
map执行中内存溢出代表了所有map类型的操作。包括:flatMap,filter,mapPatitions等。
shuffle后内存溢出的shuffle操作包括join,reduceByKey,repartition等操作。
spark中的内存
参考:https://www.cnblogs.com/frankdeng/p/9301783.html
spark的Executor的Container内存有两大部分组成:堆外内存和Executor内存。
堆外内存
有spark.yarn.executor.memeoryOverhead参数设置。如果没有设置,则使用
val executorMemoryOverhead = sparkConf.getInt("spark.yarn.executor.memoryOverhead",math.max((MEMORY_OVERHEAD_FACTOR * executorMemory).toInt, MEMORY_OVERHEAD_MIN))
MEMORY_OVERHEAD_FACTOR = 0.10默认为0.01;
// 最终分配的executor 内存为 两部分的和
val executorMem = args.executorMemory + executorMemoryOverhead
来获得。
spark在一个Executor中的内存分为三部分:
1、execution块,shuffle的数据也会先缓存在这个内存中,满了再写入磁盘中、排序、map的过程也是在这个内存中执行的、聚合、计算的内存。
2、storage块,用于集群中缓存RDD和传播内部数据的内存(cache、persist数据的地方、广播变量)
3、other块,程序执行时预留给自己的内存,如spark程序的对象。
execution块和storage块占Executor的大部分,而other占有一小部分。在spark1.6之后,execution内存和storage内存可以相互借用,提高了内存的spark中内存的使用率,同时也减少了OMM的情况。
**在实际分配Executor内存的计算,需要涉及在yarn上的部署模式**
关于Executor计算的相关公式,见源码:
var executorMemory = 1024 // 默认值,1024MB
val MEMORY_OVERHEAD_FACTOR = 0.10 // OverHead 比例参数,默认0.1
val MEMORY_OVERHEAD_MIN = 384
val executorMemoryOverhead =sparkConf.getInt("spark.yarn.executor.memoryOverhead",
math.max((MEMORY_OVERHEAD_FACTOR * executorMemory).toInt, MEMORY_OVERHEAD_MIN))
// 假设有设置参数,即获取参数,否则使用executorMemoryOverhead 的默认值
val executorMem = args.executorMemory + executorMemoryOverhead
// 最终分配的executor 内存为 两部分的和
提交的脚本:spark-submit --master yarn-cluster --name test --driver-memory 6g --executor-memory 6g
总得大小应该为6144MB+612MB=6756MB
然而实际的开销为7168,这是为什么呢?
这会涉及到规整化因子
规整化因子的介绍
为了易于管理资源和调度资源,Hadoop YARN内置了资源规整化算法,它规定了最小可申请资源量、最大可申请资源量和资源规整化因子,如果应用程序申请的资源量小于最小可申请资源量,则YARN会将其大小改为最小可申请量,也就是说,应用程序获得资源不会小于自己申请的资源,但也不一定相等;如果应用程序申请的资源量大于最大可申请资源量,则会抛出异常,无法申请成功;规整化因子是用来规整化应用程序资源的,应用程序申请的资源如果不是该因子的整数倍,则将被修改为最小的整数倍对应的值,公式为:
ceil(a/b)*b,其中a是应用程序申请的资源,b为规整化因子。
比如,在yarn-site.xml中设置,相关参数如下:
yarn.scheduler.minimum-allocation-mb:最小可申请内存量,默认是1024
yarn.scheduler.minimum-allocation-vcores:最小可申请CPU数,默认是1
yarn.scheduler.maximum-allocation-mb:最大可申请内存量,默认是8096
yarn.scheduler.maximum-allocation-vcores:最大可申请CPU数,默认是4
对于规整化因子,不同调度器不同,具体如下:
FIFO和容量调度器(Capacity Scheduler),规整化因子等于最小可申请资源量,不可单独配置。
Fair Scheduler:规整化因子通过参数yarn.scheduler.increment-allocation-mb和yarn.scheduler.increment-allocation-vcores设置,默认是1024和1。
比如yarn的最小可申请资源内存量为1024,规整因子是1024,如果一个应用程序申请1500内存,则会得到2048,如果规整因子是512,则得到的内存1536。
所以最终分配的内存为:ceil(6756/1024)*1024=7168
Client 和 Cluster 内存分配的差异
在使用Clietn 和 Cluster 两种方式提交时,资源开销占用也是不同的。
不管CLient或CLuster模式下,ApplicationMaster都会占用一个Container来运行;而Client模式下的Container默认有1G内存,1个cpu核,Cluster模式下则使用driver-memory和driver-cpu来指定;
小结:虽然spark中用executor-memory参数控制那个executor的内存,但是,实际分配多少内存,还有一定的处理机制,需要跟踪源码来发掘
1、map过程产生大量对象导致内存溢出
这种溢出的原因:是在单个map中产生了大量的对象导致的。
**解决方法:**通过减少每个task的大小来减少Executor内存中的数量,具体做法是在调用map操作前先调用repartition方法,增大分区数来减少每个分区的大小,再传入map中进行操作。
2、数据不平衡导致内存溢出
数据不平衡除了可能导致内存溢出外,也可能导致性能的问题,解决方法和上面的类似。
3、shuffle后内存溢出
shuffle内存溢出的情况可以说都是shuffle后,shuffle会产生数据倾斜,少数的key内存非常的大,它们都在同一个Executor中计算,导致运算量加大甚至会产生OOM。