spark executor出现OOM内存溢出问题解决方案

转载链接:https://blog.csdn.net/wypblog/article/details/104935712/

https://segmentfault.com/a/1190000019157848

为什么会出现OOM?

1,当有多个 Task 同时在 Executor 上执行时, 将会有多个 TaskMemoryManager 共享 MemoryManager 管理的内存。那么 MemoryManager 是怎么分配的呢?答案是每个任务可以分配到的内存范围是 [1 / (2 * n), 1 / n],其中 n 是正在运行的 Task 个数。因此,多个并发运行的 Task 会使得每个 Task 可以获得的内存变小。

2,前面提到,在 MemoryConsumer 中有 Spill 方法,当 MemoryConsumer 申请不到足够的内存时,可以 Spill 当前内存到磁盘,从而避免无节制的使用内存。但是,对于堆内内存的申请和释放实际是由 JVM 来管理的。因此,在统计堆内内存具体使用量时,考虑性能等各方面原因,Spark 目前采用的是抽样统计的方式来计算 MemoryConsumer 已经使用的内存,从而造成堆内内存的实际使用量不是特别准确。从而有可能因为不能及时 Spill 而导致 OOM。

3、Executor 中任务以线程的方式执行,各线程共享JVM的资源(即 Execution 内存),任务之间的内存资源没有强隔离(任务没有专用的Heap区域)。因此,可能会出现这样的情况:先到达的任务可能占用较大的内存,而后到的任务因得不到足够的内存而挂起。

在 Spark 任务内存管理中,使用 HashMap 存储任务与其消耗内存的映射关系。每个任务可占用的内存大小为潜在可使用计算内存( 潜在可使用计算内存为: 初始计算内存 + 可抢占存储内存)的 1/2n ~ 1/n,当剩余内存为小于 1/2n 时,任务将被挂起,直至有其他任务释放执行内存,而满足内存下限 1/2n,任务被唤醒。其中 n 为当前 Executor 中活跃的任务树。

比如如果 Execution 内存大小为 10GB,当前 Executor 内正在运行的 Task 个数为5,则该 Task 可以申请的内存范围为 10 / (2 * 5) ~ 10 / 5,也就是 1GB ~ 2GB 的范围。

任务执行过程中,如果需要更多的内存,则会进行申请,如果存在空闲内存,则自动扩容成功,否则,将抛出 OutOffMemroyError。

 

 

解决:

Executor OOM类错误 (错误代码 137、143等)

该类错误一般是由于 Heap(M2)已达上限,Task 需要更多的内存,而又得不到足够的内存而导致。因此,解决方案要从增加每个 Task 的内存使用量,满足任务需求 或 降低单个 Task 的内存消耗量,从而使现有内存可以满足任务运行需求两个角度出发。因此有如下解决方案:

法一:增加单个task的内存使用量

增加最大 Heap值,即上图中 M2 的值,使每个 Task 可使用内存增加。

降低 Executor 的可用 Core 的数量 N , 使 Executor 中同时运行的任务数减少,在总资源不变的情况下,使每个 Task 获得的内存相对增加。当然,这会使得 Executor 的并行度下降。可以通过调高 spark.executor.instances 参数来申请更多的 executor 实例(或者通过 

spark.dynamicAllocation.enabled 启动动态分配),提高job的总并行度。

法二:降低单个Task的内存消耗量

降低单个Task的内存消耗量可从配置方式和调整应用逻辑两个层面进行优化:

  • 配置方式

    减少每个 Task 处理的数据量,可降低 Task 的内存开销,在 Spark 中,每个 partition 对应一个处理任务 Task,因此,在数据总量一定的前提下,可以通过增加 partition 数量的方式来减少每个 Task 处理的数据量,从而降低 Task 的内存开销。针对不同的 Spark 应用类型,存在不同的 partition 配置参数 :

 
  1. P = spark.default.parallism (非SQL应用)

  2. P = spark.sql.shuffle.partition (SQL 应用)

(滑动可查看)

通过增加 P 的值,可在一定程度上使 Task 现有内存满足任务运行。注: 当调整一个参数不能解决问题时,上述方案应进行协同调整。

a.调整应用逻辑

Executor OOM 一般发生 Shuffle 阶段,该阶段需求计算内存较大,且应用逻辑对内存需求有较大影响,下面举例就行说明:

选择合适的算子,如 groupByKey 转换为 reduceByKey。

一般情况下,groupByKey 能实现的功能使用 reduceByKey 均可实现,而 ReduceByKey 存在 Map 端的合并,可以有效减少传输带宽占用及 Reduce 端内存消耗。

b.避免数据倾斜 (data skew)

Data Skew 是指任务间处理的数据量存大较大的差异。

如左图所示,key 为 010 的数据较多,当发生 shuffle 时,010 所在分区存在大量数据,不仅拖慢 Job 执行(Job 的执行时间由最后完成的任务决定)。而且导致 010 对应 Task 内存消耗过多,可能导致 OOM。

而右图,经过预处理(加盐,此处仅为举例说明问题,解决方法不限于此)可以有效减少 Data Skew 导致的问题。

NOTE

上述举例仅为说明调整应用逻辑可以在一定程序上解决OOM问题,解决方法不限于次。

(4)Execution Memory Spill Heuristic

  • 现象:在 stage 3 发现执行内存溢出。Shuffle read bytes 和 spill 分布均匀。这个 stage 有 200 个 tasks。

  • 原因:执行内存溢出,意味着执行内存不足。跟上面的 OOM 错误一样,只是执行内存不足的情况下不会报 OOM 而是会将数据溢出到磁盘。但是整个性能很难接受。

  • 解决方案:同 3。

(5) Executor GC Heuristic

  • 现象:Executor 花费很多时间在 GC。

  • 原因:可以通过-verbose:gc

    -XX:+PrintGCDetails

    -XX:+PrintGCTimeStamps 查看 GC 情况

  • 解决方案: Garbage Collection Tuning

(6)Beyond … memory, killed by yarn.

出现该问题原因是由于实际使用内存上限超过申请的内存上限而被 Yarn 终止掉了, 首先说明 Yarn 中 Container 的内存监控机制:

  • Container 进程的内存使用量 : 以 Container 进程为根的进程树中所有进程的内存使用总量。

  • Container 被杀死的判断依据 : 进程树总内存(物理内存或虚拟内存)使用量超过向 Yarn 申请的内存上限值,则认为该 Container 使用内存超量,可以被“杀死”。

因此,对该异常的分析要从是否存在子进程两个角度出发。

a. 不存在子进程

Overhead) 不足,依据 Yarn 内存使用情况有如下两种方案:

法一:如果,M (spark.executor.memory) 未达到 Yarn 单个 Container 允许的上限时,可仅增加 M1(spark.yarn.executor.memoryOverhead),从而增加 M;如果,M 达到 Yarn 单个 Container 允许的上限时,增加 M1,降低 M2。

注意二者之各要小于 Container 监控内存量,否则伸请资源将被 yarn 拒绝。

法二:减少可用的 Core 的数量 N,使并行任务数减少,从而减少 Overhead 开销

b. 存在子进程

Spark 应用中 Container 以 Executor(JVM进程)的形式存在,因此根进程为 Executor 对应的进程,而 Spark 应用向Yarn申请的总资源 M = M1 + M2,都是以 Executor(JVM) 进程(非进程树)可用资源的名义申请的。

申请的资源并非一次性全量分配给 JVM 使用,而是先为 JVM 分配初始值,随后内存不足时再按比率不断进行扩容,直致达到 Container 监控的最大内存使用量 M。当 Executor 中启动了子进程(如调用 shell 等)时,子进程占用的内存(记为 S)就被加入 Container 进程树,此时就会影响 Executor 实际可使用内存资源(Executor 进程实际可使用资源变为: M - S),然而启动 JVM 时设置的可用最大资源为 M,且 JVM 进程并不会感知 Container 中留给自己的使用量已被子进程占用。因此,当 JVM 使用量达到 M - S,还会继续开劈内存空间,这就会导致 Executor 进程树使用的总内存量大于 M 而被 Yarn 杀死。

典形场景有:

  • PySpark(Spark已做内存限制,一般不会占用过大内存)

  • 自定义Shell调用

其解决方案分别为:

a. PySpark场景:

  • 如果,M 未达到 Yarn 单个 Container 允许的上限时,可仅增加 M1 ,从而增加 M;如果,M 达到 Yarn 单个 Container 允许的上限时,增加 M1,降低 M2。

  • 减少可用的 Core 的数量 N,使并行任务数减少,从而减少 Overhead 开销

b. 自定义 Shell 场景:(OverHead 不足为假象)

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值