OOM事件
一、概念
内存溢出,本质就是「某一端(Executor/Driver)要处理的数据量 / 内存占用,超过了它被分配的内存上限,Spark 4.x 中 95% 的 OOM 发生在「Executor 端」,5% 发生在「Driver 端」;
二、现像介绍
2.1 Executor端:
发生问题基本是在shuffle阶段,内容可以参考:https://www.cnblogs.com/data-agent/p/19444006
Broadcast广播表过大导致的 OOM,默认10MB的小表会广播
全表加载 / 笛卡尔积 导致的 OOM:触发笛卡尔积(A 表 1000 万行 × B 表 1000 万行 = 1 万亿行)
数据倾斜:聚合/窗口函数 海量数据一次性计算,对亿级数据直接做GROUP BY 低基数字段(比如按性别分组),
窗口函数OVER(PARTITION BY 倾斜字段),比如按user_id=1分区,该分区有 1 亿条数据;
Python UDF 的内存是独立的,写了复杂的自定义UDF,需要调大 Executor 的 Python 内存:.config("spark.executor.pyspark.memory", "2g")
2.2 Driver端:
第七章的方法会把全量大数据拉到Driver端,而Driver的内存一般默认只有8G,根本装不下海量数据
广播超大变量到Executor,在 Driver 端创建超大的 List/Map/ 字典(比如 10GB 的配置文件),然后用spark.broadcast()广播到 Executor;
三、排查
第一步:
看日志,定位置 → Executor 还是 Driver?
日志有Java heap space → Executor OOM;
日志有Driver memory → Driver OOM;
第二步:看 Spark UI,定场景 → 是什么操作导致的?
看「SQL」页面:如果 OOM 发生在 Shuffle 阶段(Join/GroupBy)→ Shuffle OOM;
看「Storage」页面:如果有超大的广播表 → 广播表 OOM;
看「Jobs」页面:如果 Task 处理的数据量超大 → 数据裁剪不足;
看「Storage」:找到Broadcast Variables找到每个广播变量的大小 →广播变量OOM
五、常用优化语句
from pyspark.sql import SparkSession spark = SparkSession.builder \ .appName("Spark4x_Anti_OOM") \ # 核心AQE全开启(防OOM+性能优化) .config("spark.sql.adaptive.enabled", "true") .config("spark.sql.adaptive.coalescePartitions.enabled", "true") .config("spark.sql.adaptive.join.enabled", "true") .config("spark.sql.adaptive.skewJoin.enabled", "true") .config("spark.sql.adaptive.aggregate.enabled", "true") # 核心并行度(解决Shuffle OOM,大表必调) .config("spark.sql.shuffle.partitions", "1000") # 广播阈值(防广播OOM) .config("spark.sql.autoBroadcastJoinThreshold", "20971520") # 内存核心优化(Executor防OOM) .config("spark.memory.offHeap.enabled", "true") .config("spark.memory.offHeap.size", "4g") # Driver防OOM(生产必加) .config("spark.driver.maxResultSize", "4g") # 倾斜阈值适配大表 .config("spark.sql.adaptive.skewJoin.skewThreshold", "2147483648") .getOrCreate()
六、核心总结
Executor OOM 看「Shuffle / 广播」,Driver OOM 看「collect / 广播变量」
Spark4.x 最优组合拳:AQE + 合理并行度 + 数据裁剪 = 防 OOM 天花板
七、导致Driver端OOM方法
df.collect() —— 最最最常用的高危方法,返回全量数据的 List 数组,大表用必卡死
df.collectAsList() —— 和 collect 一致,只是返回格式是 Java List,风险等同
df.toPandas() —— PySpark 专属,把分布式 DF 转成单机的 Pandas DF,超大 DF 必 OOM
df.toLocalIterator() —— 看似分批拉取,实则底层还是加载全量数据到 Driver,大表必 OOM
df.foreach(lambda x: 逻辑) —— Driver 端执行的遍历,遍历全量数据,大表必 OOM
解决方法:
✔️ 替代方案:如果需要处理全量数据 → 用 df.repartition(n).foreachPartition(逻辑),在 Executor 端分布式处理,数据永远不拉到 Driver;
✔️ 替代方案:如果需要看数据 → 永远用 df.show(50),只看前 50 行足够排查;
✔️ 替代方案:如果需要计数 → 用 df.countApprox() 替代精准 count,或用 SQL 的select count(1) from table(Executor 分布式计算)。
浙公网安备 33010602011771号