Spark 2.3.0 Driver 内存泄漏

Spark 2.3.0 Driver 内存泄漏

问题描述

线上StructedStreaming随着运行时间变长,处理能力越来越慢,重启程序后恢复到之前的处理能力。在测试环境复现问题之后,发现Driver进程的老年代和方法区被占满,且每次垃圾回收释放的内存空间有限,导致JVM一直在FullGC,怀疑是内存泄漏导致的,遂Dump出内存快照。
Spark 2.3.0 Driver 内存泄漏_第1张图片

内存快照分析

使用MAT(Memory Analysis Tool)分析内存快照,得到以下报告:
Spark 2.3.0 Driver 内存泄漏_第2张图片
两个对象占据了绝大多数的内存空间,确实存在内存泄漏的嫌疑。继续追查有问题的对象。

Spark 2.3.0 Driver 内存泄漏_第3张图片

发现一共存在两个内存泄漏的问题,分别占据了 47%和49%的内存空间

问题定位

问题1:

org.apache.spark.status.ElementTrackingStore 内存泄漏:
Spark 2.3.0 Driver 内存泄漏_第4张图片

查看AppStatusStore在创建ElementTrackingStore实例过程中,确实使用了一个InMemoryStore的实例。符合内存分析报告中,泄漏对象的引用关系。
Spark 2.3.0 Driver 内存泄漏_第5张图片
所以目前定位是这个store对象出现了内存泄漏。
跟踪代码定位哪些地方操作了这个对象。发现在AppStatusStore.taskSummary方法里调用了向store中写数据的逻辑:
Spark 2.3.0 Driver 内存泄漏_第6张图片
而只有在SparkContext结束的时候才会释放store中的对象。
Spark 2.3.0 Driver 内存泄漏_第7张图片

问题2:

org.apache.spark.sql.execution.streaming.state.HDFSBackedStateStoreProvider 内存泄漏:
Spark 2.3.0 Driver 内存泄漏_第8张图片
Spark 2.3.0 Driver 内存泄漏_第9张图片
Spark 2.3.0 Driver 内存泄漏_第10张图片

快照显示是一个state-store-maintenance-task线程持有了197个Tuple,每个tuple的第二个成员都是一个HDFSBackedStateStoreProvider对象。追踪state-store-maintenance-task线程源码。

Spark 2.3.0 Driver 内存泄漏_第11张图片

定义state-store-maintenance-task线程的类的父类是StateStore,他持有一个成员变量是loadedProviders,印证内存快照,正是这个对象发生了内存泄漏。这个对象只在StateStore关闭或者state-store-maintenance-task线程出错时才会释放引用。

解决方法

问题1:

解决思路:
为了兼容线上运行的Spark任务,决定暂时不替换新版本,通过减少对sotre对象的写入来限制元信息对内存的占用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJxSXDZ2-1572169353864)(media/15718891931372/15719022397240.jpg)]
追踪代码发现只有在Spark UI和查询任务状态的API中调用了AppStatusStore.taskSummary方法。于是减小Spark保留的元信息的任务个数,并观察影响:
spark.ui.retainedStages=50
spark.ui.retainedJobs=50
spark.ui.retainedTasks=500

问题2:

解决思路:
为了兼容线上运行的Spark任务,决定暂时不替换新版本,通过减少对loadedProviders对象的写入来限制元信息对内存的占用。
在Apache的Jira中也发现了这个问题:
https://issues.apache.org/jira/browse/SPARK-24717
Spark 2.3.0 Driver 内存泄漏_第12张图片
大概意思是说,Spark会默认保留最近100个批次的信息,导致内存占用过大。这个问题在2.4解决了。在不更换版本的情况下,我通过调整spark.sql.streaming.minBatchesToRetain参数减少Spark保留的批次数,继续观察影响。

你可能感兴趣的:(Java)