背景

长话短说,我们部门一个同事找到我,说他的spark 2.3 structured streaming程序频繁报OOM,从来没有坚持过超过三四天的,叫帮看一下。

这种事情一般我是不愿意看的,因为大部分情况下spark oom就那么几种可能:

  • 数据量拉太大,executor内存爆了;
  • shuffle过程中数据量太大,shuffle数太少,内存又爆了;
  • 闲着蛋疼调用collect之类的方法,把数据往dirver上一聚合,driver内存爆了
  • 闲着蛋疼又调用了一下persist还把结果存内存,还是爆了

这些问题基本都可以通过限制每次拉取的数据/加大内存/该分片分片解决。

但这次这个我看了一下,还真不是上面这些日常问题,值得记录一下。

过程

过了一遍程序和数据,肉眼感觉没毛病,这些地方都没问题,只好祭出大杀器:

-XX:+HeapDumpOnOutOfMemoryError

顺便还加上了printGC全家桶。

程序再次挂掉后,先看了一眼gc日志,发现老年代内存使用量持续增大,fgc前后几乎无变化,那么就不是数据太大装不下,应该是内存泄漏没跑了,再看dump文件。

拿MAT打开文件,很容易就定位到了内存泄漏点,如下图所示:



直奔源码:

public class InMemoryStore implements KVStore {

  private Object metadata;
//这里就是那个5个多g大的map
private ConcurrentMap<Class<?>, InstanceList> data = new ConcurrentHashMap<>(); ......
}

没毛病,能对上。所以问题应该比较清晰了,spark应该是每次执行batch时在什么地方往这个map里加了很多数据,但是又忘记了移除掉已经过期的部分,所以导致gc无效了。

那接下来要问的就是,什么地方做了put操作而又没有remove呢?我们再来看看下这个5个g的InmemoryStore的引用到底被谁持有:

图里很明显,接下来我们要看ElementTrackingStore的实现,我顺便把这个类的说明也放在这里:

/**
* A KVStore wrapper that allows tracking the number of elements of specific types, and triggering
* actions once they reach a threshold. This allows writers, for example, to control how much data
* is stored by potentially deleting old data as new data is added.
*
* This store is used when populating data either from a live UI or an event log. On top of firing
* triggers when elements reach a certain threshold, it provides two extra bits of functionality:
*
* - a generic worker thread that can be used to run expensive tasks asynchronously; the tasks can
* be configured to run on the calling thread when more determinism is desired (e.g. unit tests).
* - a generic flush mechanism so that listeners can be notified about when they should flush
* internal state to the store (e.g. after the SHS finishes parsing an event log).
*
* The configured triggers are run on a separate thread by default; they can be forced to run on
* the calling thread by setting the `ASYNC_TRACKING_ENABLED` configuration to `false`.
*/
private[spark] class ElementTrackingStore(store: KVStore, conf: SparkConf) extends KVStore { import config._ private val triggers = new HashMap[Class[_], Seq[Trigger[_]]]()
private val flushTriggers = new ListBuffer[() => Unit]()
private val executor = if (conf.get(ASYNC_TRACKING_ENABLED)) {
ThreadUtils.newDaemonSingleThreadExecutor("element-tracking-store-worker")
} else {
MoreExecutors.sameThreadExecutor()
} @volatile private var stopped = false /**
* Register a trigger that will be fired once the number of elements of a given type reaches
* the given threshold.
*
* @param klass The type to monitor.
* @param threshold The number of elements that should trigger the action.
* @param action Action to run when the threshold is reached; takes as a parameter the number
* of elements of the registered type currently known to be in the store.
*/
def addTrigger(klass: Class[_], threshold: Long)(action: Long => Unit): Unit = {
val existing = triggers.getOrElse(klass, Seq())
triggers(klass) = existing :+ Trigger(threshold, action)
} ......
}

这个类的方法里,我们需要关注的就是这个addTrigger方法,其注释也写的很明白,就是用来当保存的对象达到一定数目后触发的操作。

这时候心里就猜一下是不是什么地方的trigger写错了,所以我们再看看这个方法都在哪里使用了:



考虑到我们溢出的对象都是SparkPlanGraphNode,所以先看最下面我选中的蓝色那一行的代码:

kvstore.addTrigger(classOf[SQLExecutionUIData], conf.get(UI_RETAINED_EXECUTIONS)) { count =>
cleanupExecutions(count)
} private def cleanupExecutions(count: Long): Unit = {
val countToDelete = count - conf.get(UI_RETAINED_EXECUTIONS)
if (countToDelete <= 0) {
return
} val view = kvstore.view(classOf[SQLExecutionUIData]).index("completionTime").first(0L)
val toDelete = KVUtils.viewToSeq(view, countToDelete.toInt)(_.completionTime.isDefined)
//出错的就是这一行
toDelete.foreach { e => kvstore.delete(e.getClass(), e.executionId) }
}

看到了吧,这里在触发trigger的时候,压根没有删除SparkPlanGraphWrapper的相关逻辑,难怪会报oom!

结果

按理说到这里就差不多了,这个OOM的锅还真不能让同事背,的确是spark的一个bug。但是我很好奇,这么大一个问题,spark社区难道就没有动静吗?所以我就去社区搜了一下,发现了这个:

Memory leak of SparkPlanGraphWrapper in sparkUI

所以确认了,这个地方确实是spark2.3的一个隐藏bug,在2.3.1和2.4.0中被修复了,有兴趣的童鞋可以点进去看看。


全文完。

spark 2.3 导致driver OOM的一个SparkPlanGraphWrapper源码的bug的更多相关文章

  1. 一个lucene源码分析的博客

    ITpub上的一个lucene源码分析的博客,写的比较全面:http://blog.itpub.net/28624388/cid-93356-list-1/

  2. Spark Job的提交与task本地化分析(源码阅读八)

    我们又都知道,Spark中任务的处理也要考虑数据的本地性(locality),Spark目前支持PROCESS_LOCAL(本地进程).NODE_LOCAL(本地节点).NODE_PREF.RACK_ ...

  3. Spark Shuffle数据处理过程与部分调优(源码阅读七)

    shuffle...相当重要,为什么咩,因为shuffle的性能优劣直接决定了整个计算引擎的性能和吞吐量.相比于Hadoop的MapReduce,可以看到Spark提供多种计算结果处理方式,对shuf ...

  4. 6.Spark streaming技术内幕 : Job动态生成原理与源码解析

    原创文章,转载请注明:转载自 周岳飞博客(http://www.cnblogs.com/zhouyf/)   Spark streaming 程序的运行过程是将DStream的操作转化成RDD的操作, ...

  5. 最近建了一个.net源码共享群,群共享有大量网友分享的.net(C#)商业源码

    .net源码共享群 324087998. 本群创建于2013/6/21: 群里都是.net(C#)程序开发人员,群共享有大量网友分享的.net(C#)商业源码.比如:DTCMS旗舰版,hishop微分 ...

  6. Spark Job的提交与task本地化分析(源码阅读)

    Spark中任务的处理也要考虑数据的本地性(locality),Spark目前支持PROCESS_LOCAL(本地进程).NODE_LOCAL(本地节点).NODE_PREF.RACK_LOCAL(本 ...

  7. Spark集群任务提交流程----2.1.0源码解析

    Spark的应用程序是通过spark-submit提交到Spark集群上运行的,那么spark-submit到底提交了什么,集群是怎样调度运行的,下面一一详解. 0. spark-submit提交任务 ...

  8. android Popupwindow 的一个demo源码

    一直想用一下PopupWindow,就是苦于没有demo,自己去研究有太懒,刚好最近研究推送,下载了一个腾讯信鸽的demo,里面用到了一个PopupWindow,效果还不错,弄下来记录一下: 1.核心 ...

  9. 分享一个jdk源码链接

    请查看下面的链接:http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/bcba89ce0a8c/src/share/classes/,进入页面后,点击列表中 ...

随机推荐

  1. mysql之全球化和本地化:字符集、校对集、中文编码问题

    本文内容: 什么是字符集?什么是校对集? 查看字符集和校对集 设置字符集和校对集 mysql中的中文数据问题 首发日期:2018-04-19 什么是字符集?什么是校对集? 字符集是字母和符号的集合,每 ...

  2. swipe使用及竖屏页面滚动方法

    基于swipe4写了一个移动端的全屏滚动效果  但是图片始终不能自适应屏幕设备大小  这里记录一下 开始的时候要设置  移动端配置 <meta name="viewport" ...

  3. 口碑点餐相关问题FAQ

    1.菜品上传中:出现重复错误或者违禁词 检查并修改商家中心本次上传中的重复菜品,或者删除口碑掌柜以及第三方平台已添加的重复菜品(重复菜品临时快捷办法:修改菜品名称) 2.手持pos 打开自动接单,无响 ...

  4. Python基于dtw实现股票预测【多线程】

    # -*- coding: utf-8 -*- """ Created on Tue Dec 4 08:53:08 2018 @author: zhen "&q ...

  5. SpringBoot Controller 中 HttpServletRequest ServletInputStream 读取不到数据该怎么处理

    在Springboot程序启动后,会默认添加OrderedCharacterEncodingFilter和HiddenHttpMethodFilter过滤器.在HiddenHttpMethodFilt ...

  6. shell linux 环境下循环遍历文件夹下所有文件

    demofun(){ ` do if test -f $file then echo "file: $file" elif test -d $file then echo &quo ...

  7. Java多线程 Socket使用

    点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更新中~ 正式篇: Java多线程(一) 什么是线程 Java多线程(二)关于多线程的CPU密集型和IO密集型这件事 Java多线程(三)如何 ...

  8. 爬楼梯的golang实现

    假设你正在爬楼梯.需要 n 阶你才能到达楼顶. 每次你可以爬 1 或 2 个台阶.你有多少种不同的方法可以爬到楼顶呢? 注意:给定 n 是一个正整数. 输入: 输出: 解释: 有两种方法可以爬到楼顶. ...

  9. 《Java大学教程》—第20章 文件处理

    记录():一个单独的数据实例.域():一个属性. 20.3    输入和输出设备:P484输入过程和输出过程.操作系统负责建立三个流(stream):标准输入流(System.in).标准输出流(Sy ...

  10. Loj #3055. 「HNOI2019」JOJO

    Loj #3055. 「HNOI2019」JOJO JOJO 的奇幻冒险是一部非常火的漫画.漫画中的男主角经常喜欢连续喊很多的「欧拉」或者「木大」. 为了防止字太多挡住漫画内容,现在打算在新的漫画中用 ...