Spark学习笔记:OutOfMemoryError-Direct buffer memory (OOM)

之前也遇到过几次关于OOM(堆外内存溢出)的问题,但都只是大体上看了看,没有细致的总结,目前了解的还不是特别清楚,只好总结一下我觉得可行的处理方案,另外贴一些原理。


首先是当时的一些处理方案:


第一次OOM:第一次遇到这个问题时,上网查,发现很多人都说要更改运行内存之类的,但本人是个小白,而且这个job是提交到集群上的,也不敢乱改,一般来说都是够的。我的数据量大约是在千万,接近亿级的数据,其中主要的操作是以userId为key的reduceByKey,询问了一下同事,他提出有可能是数据不平衡导致的节点资源分布不均匀(id开头为1,2的明显比较多),有些节点负载过大,而其他节点负载很小,导致分配内存时浪费了许多。

解决方案:将userId做了反转,基本可以保证数据均衡,因为末尾数字出现频率大约一致。


第二次OOM:运行了一段时间后再次OOM,这次原因就比较显而易见了,我在存文件之前将所有数据repartion(1)了,改大就好了。

解决方案:增大partition数量。当我改到16的时候,就不再出现OOM的问题了。


第三次OOM:这次有点手足无措了,一开始以为是保存的时候出的问题,后来仔细查看了spark job每一个步骤所使用的资源,shuffle read和right的数据量,以及失败次数,才发现是我在收集完5~6个数据源的数据后,做了这样一个操作

Await
      .result(futures, 5.hours)
      .reduce(_ union _)
也就是将所有数据合并到了一起,之后再做reduce。在union的时候溢出了。(后来忘记在哪里看到了,union操作所使用的内存量和其他操作好像是不一样的?)

解决方案: 我把这段改成了以下这样

val resList = Await
      .result(futures, 5.hours)
    var res = resList.head
    resList.foreach(data => {
      if (data != resList.head)
        res = res
          .union(data)
          .reduceByKey(reduceUserData(_: VipUserData, _: VipUserData))
    })
每次都union后再reduce,问题解决了。


--------------------------------我是讲解的分割线--------------------------------

OOM可能的原因有以下几点:

1. 用户代码

off heap: 资源释放不当, 例如加载文件资源次数过多, 且不正常关闭, 例如多次调用ClassLoader().getResourceAsStream

2. Driver端

DirectMemory: 拉取Executor端Task Result数据回Driver节点时, 此处消耗的DirectMemory内存 = conf.getInt("spark.resultGetter.threads", 4) * TaskResultSize

3. Executor端

Executor可能消耗的情况如下:

(1)Direct Memory: RDD.cache()/RDD.persist()操作,

因为会涉及到拉取remote RDD Block时出现Direct OOM, 此时消耗的Direct Memory = 拉取的RDDBlockSize.

Tips: 查看RDD Block Size步骤: SparkUI->Storage Tabs -> 看众多RDD中Memory或者Disk中的totalSize/cached Partitions中最大的RDD, 点进去看详情页, 然后对RDD的大小 按照Memory或者Disk排序, 找到最大的RDD Block....

如下图标记处: storage tabmax rdd block

(2)Direct Memory, 拉取Shuffle数据时出现Direct OOM

此时消耗的Direct Memory 通常= max(某个Shuffle Block的size, 50MB)

Tips: 可以在抛出该Direct OOM的Executor节点上检查是否有如下日志: Spark会在如果单个shuffleBlock的大小>1MB时输出该语句.

shuffle block

还有一种预估的方式, 前一阶段Stage 对其内的每个Task的Shuffle Write排序, 找到最大的Shuffle Write / 下一stage的task个数, 即为一个预估的shuffle Block大小.

(3)Direct Memory, RDD.persist(StorageLevel.DISK_ONLY)/RDD.persit(StorageLevel.MEMORY_AND_DISK)/RDD.persist(StorageLevel.MEMORY_AND_DISK_SER)等含有disk level的cache rdd操作.会带来额外Direct Memory消耗, 最多64MB * 3
(4)Off-Heap, RDD.persist(StorageLevel.DISK_ONLY)/RDD.persit(StorageLevel.MEMORY_AND_DISK)/RDD.persist(StorageLevel.MEMORY_AND_DISK_SER)等含有disk level的cache RDD操作.

一般情况最大的值是整个作业中最大的disk level的RDD Block的size. 但除了些许特殊操作: zip类操作(包括相同partitoner的RDD做union操作, 因为会被Spark后台优化成zip操作) 使用的size等于该操作zip的rdd中涉及的所有disk level rdd block size之和.

此部分Size如第(1)点所示, 找到Disk中最大的RDD Block即可.

4. 其他框架:
Off-Heap 涉及到读Hbase时会消耗比较多的off-heap内存, 但这部分已经通过参数(spark.hadoop.hbase.ipc.client.connection.maxidletime)控制使用上限制在256MB.
Direct Memory, 读写Parquet+Snappy, 如果采用小米内部的parquet mdh版本已经控制至多64MB, 内部Spark平台已经默认采用, 如果是其他版本的则不可控制.

Spark堆外内存控制参数:

堆外内存的使用总量 = jvmOverhead(off heap) + directMemoryOverhead(direct memory) + otherMemoryOverhead

参数描述默认值
spark.yarn.executor.jvmMemoryOverheadoff heap内存控制max(0.1 * executorMemory, 384MB)
spark.yarn.executor.directMemoryOverheadDirect Memory的控制参数256MB
spark.yarn.driver.jvmMemoryOverhead同Executor 
spark.yarn.driver.directMemoryOverhead同Executor 
spark.yarn.executor.memoryOverhead统筹参数, 如果设置了该值m, 会自动按比例分配off heap给jvmOverhead和directMemory, 分配比例为jvmOverhead = max(0.1 * executorMemory, 384MB), directMemoryOverhead =m - jvmOverhead
spark.yarn.driver.memoryOverhead同Executor

解决思路:

合理的参数推荐:

一般推荐总值:


spark.yarn.executor.directMemoryOverhead = 

{ if 存在memory level or disk level 的 block then  第1点的Size else 0 } +

{if Shuffle阶段抛出Direct OOM then 第2点的Size else 0} +

{if 存在Disk level的Block then 第3点的192MB else 0} +

{ if 存在其他框架的 then 其他框架的size else 0} + 

256MB


spark.yarn.executor.jvmOverhead = 

{ if 存在disk level的Block then 第4点的Size  else 0 } + 

{ if 存在其他框架的 then 其他框架的size else 0} +

max(executor-memory * 0.1, 384)

// 如果没有Executor表现为堆外内存使用超出, 则不需要手动调整.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值