Spark 任务失败排查指南:从 OOM 到数据倾斜的实战排障
系统化排查 Spark 批处理任务失败的常见根因,覆盖 OOM、Container killed、数据倾斜等高频场景
Spark 批处理任务失败的根因可以归为三大类:内存不足(OOM)、资源竞争与配置错误、数据分布异常(数据倾斜)。排查时应先看 YARN 或 Kubernetes 上的退出码和日志摘要,确认属于哪一类,再按对应路径深入排查。本文基于阿里云 EMR、社区 Spark 文档以及大规模生产实践,给出一套可复用的排障框架。
一、定位失败类型:从退出码到日志
任务失败后,第一步是查看 Application 的最终状态和退出原因。不同的退出码对应不同的故障类型,快速识别退出码可以大幅缩短排查时间。
在 YARN 模式下,使用 yarn application -status <appId> 命令查看任务状态。常见的退出场景包括:
- Container killed by YARN:退出码通常为 -1000,表示 Container 的实际内存使用量超过了 YARN 分配的资源上限,被 NodeManager 强制终止
- Exit code 137:Linux 内核的 OOM Killer 触发,进程被操作系统直接杀死。通常意味着 Container 的物理内存已耗尽
- Exit code 1:应用自身抛出了未捕获异常,需要查看 Driver 或 Executor 的 stderr 日志定位具体错误
- Exit code 255:通常是启动脚本错误、类路径配置问题或 Spark 版本兼容性问题
拿到退出码后,下一步是获取详细日志。在 YARN 聚合日志开启的情况下,使用 yarn logs -applicationId <appId> 可以拉取全部日志。在 Spark History Server 中,可以查看每个 Stage 和 Task 的执行详情。重点搜索以下关键词:OutOfMemoryError、Container killed、SparkException、Shuffle fetch failed、Exceeding memory limits。
二、OOM 排查:从堆内到堆外
内存不足是 Spark 任务失败的第一大原因。根据报错位置不同,可以细分为 Driver OOM、Executor 堆内 OOM、Executor 堆外 OOM 三种场景。
2.1 Container killed by YARN for exceeding memory limits
这个错误意味着 Container 的实际内存使用量超过了 YARN 分配的 memory 加 memoryOverhead 之和。Spark 任务因为大量使用堆外内存——包括 Netty Shuffle 缓冲区、Spark Unsafe 内存、Py4J 序列化缓冲等——特别容易触发这类问题。
排查步骤如下:首先检查 spark.executor.memoryOverhead 和 spark.driver.memoryOverhead 的配置值。YARN 默认将 overhead 设置为 max(384MB, executor.memory × 0.10),对于 Shuffle 密集型任务这个默认值通常不够用。其次查看 YARN 的 Container 监控指标,确认物理内存使用量是否接近 memory 加 memoryOverhead 的总和。最后如果任务涉及读写对象存储(如 OSS 或 S3),还需要检查 fs.oss.read.readahead.buffer.count 和 fs.oss.memory.buffer.size.max.mb 等缓冲配置,这些配置会占用额外的堆外内存。
修复方案:适当增大 memoryOverhead,通常建议设置为 executor.memory 的百分之十五到二十。如果读写对象存储是主要瓶颈,可以调小 OSS 缓冲区参数来降低内存占用。
2.2 Java heap space 与 GC overhead exceeded
这类错误表明 JVM 堆内存不足。常见于两种场景:单个 Task 处理的数据量过大导致堆内存不够用,或者 Shuffle 分区数设置过低导致每个分区的数据量过大。
排查 Driver OOM 时,重点检查代码中是否存在 collect()、toLocalIterator() 或 broadcast() 大表等将大量数据拉取到 Driver 端的操作。Driver 的默认内存为 1GB,对于需要汇总大量结果的作业远远不够。
排查 Executor OOM 时,检查 spark.executor.memory 是否匹配数据量,以及 spark.sql.shuffle.partitions(默认值为 200)是否过低。如果 Shuffle 数据量达到数十 GB,200 个分区意味着每个分区需要处理上百 MB 的数据,很容易撑爆单个 Task 的堆内存。
修复方案方面:对于 Driver OOM,应避免将大数据集 collect() 到 Driver 端,改用 foreachPartitions 在 Executor 端执行聚合操作;同时可以设置 spark.sql.autoBroadcastJoinThreshold=-1 禁用自动广播以减少 Driver 端的内存压力。对于 Executor 堆内 OOM,可以增大 spark.executor.memory,同时调小 spark.executor.cores 以减少每个 Executor 上并发运行的 Task 数量,或者增大 spark.sql.shuffle.partitions 将数据分散到更多分区中处理。
2.3 读取 Snappy 压缩文件时 OOM
这是一个常见但隐蔽的问题。Spark 在使用 Java 原生 Snappy 实现解压缩时,可能因为内存管理不当导致 OOM。解决方案是在配置中启用 Snappy 原生库:设置 spark.hadoop.io.compression.codec.snappy.native=true。
离线开发 BatchWorks
支持多模态、覆盖集成开发调度运维的一站式离线数据开发平台
三、数据倾斜:隐藏的性能杀手
数据倾斜不会直接导致任务报错退出,但会使个别 Task 的处理时间远超其他 Task,最终因为超时被 YARN 终止,或者因为持续 GC 和内存压力导致 OOM。典型症状是 Stage 进度卡在百分之九十九长时间不动,或者某些 Executor 的日志中频繁出现 Full GC 警告。
3.1 识别数据倾斜
排查数据倾斜需要关注以下信号:在 Spark UI 的 Stage 页面,点击具体的 Task 查看 Shuffle Read 和 Shuffle Write 的数据量分布。如果个别 Task 的数据量是中位数的十倍以上,基本可以确认存在数据倾斜。检查 Task 的执行时间分布,倾斜场景下最慢的 Task 通常是最慢中位数的五到五十倍。对于 SQL 类任务,开启 AQE(Adaptive Query Execution)后,在 SQL 页面可以看到 Shuffle 后的实际分区数据量分布,有助于快速定位倾斜分区。
3.2 解决数据倾斜
方案一:开启 AQE 自适应查询执行。 AQE 从 Spark 3.0 开始成为推荐配置。它能在运行时根据 Shuffle 后的实际数据量动态调整分区数,自动合并过小的分区和拆分过大的分区。核心配置参数为 spark.sql.adaptive.enabled=true,配合 spark.sql.adaptive.shuffle.targetPostShuffleInputSize=64M 使用。设置目标分区大小后,AQE 会自动将超过该阈值的分区拆分,同时合并数据量过小的相邻分区。这是最简单且无侵入的方案,不需要修改任何业务代码。
方案二:Broadcast Join 消除 Shuffle。 如果倾斜发生在 Join 操作中,且其中一张表较小(通常小于数百 MB),可以强制使用 Broadcast Join 来避免 Shuffle 操作。通过设置 spark.sql.autoBroadcastJoinThreshold 为合适的阈值,或在 SQL 中使用 BROADCAST Hint,可以将小表广播到所有 Executor,直接在本地完成 Join。
方案三:两阶段聚合。 对于 Group By 聚合场景导致的倾斜,可以先给 Key 添加随机前缀做局部预聚合,再去掉前缀做全局聚合。第一阶段通过随机前缀将倾斜 Key 的数据分散到多个 Task 中处理,第二阶段将相同原始 Key 的中间结果汇总。这种方式可以在不增加全局 Shuffle 量的前提下有效缓解倾斜。
方案四:Salting 加盐打散。 对于 Join 场景的倾斜,可以在大表的 Key 上添加随机盐值进行打散,同时将小表按盐值维度膨胀相应倍数,使 Join 的数据分布趋于均匀。盐值的桶数需要根据倾斜程度调整,通常设置为 10 到 100。
数据治理
统一标准、质量、安全与资产管理,沉淀高质量数据集,支撑经营决策、监管合规与 AI 应用。
四、Shuffle 阶段的常见错误
除了 OOM 和数据倾斜,Shuffle 阶段还有一些高频错误需要关注。
Shuffle file not found 通常由 Executor 在 Shuffle 过程中失败后重试导致,旧的 Shuffle 文件已被 Spark 的内存管理器清理。解决方案是增大 spark.cleaner.periodicGCInterval 和 spark.storage.blockManagerTimeoutIntervalMs,或者启用 External Shuffle Service 将 Shuffle 数据持久化到独立进程。
Size exceeds Integer.MAX_VALUE 当单个 Shuffle 分区的数据量超过 2GB 时触发。根本原因通常是分区数太少或存在严重数据倾斜。解决方式是增大 spark.sql.shuffle.partitions 或按照上文的数据倾斜方案处理。
五、排障清单
遇到 Spark 任务失败时,建议按以下顺序系统排查:首先查看退出码,区分 OOM、配置错误和应用异常。其次搜索日志关键词定位具体报错。然后检查内存配置三件套——executor.memory、driver.memory、memoryOverhead 是否匹配实际数据量。接着检查 Shuffle 配置,特别是 spark.sql.shuffle.partitions 是否过低。之后查看 Stage 页面的 Task 数据量分布,确认是否存在数据倾斜。最后考虑开启 AQE 作为通用的第一道防线。
BatchWorks 离线开发平台已将 AQE 默认开启,并提供资源配置面板和任务重试机制,可以在平台层面减少因配置不当导致的任务失败。对于频繁失败的任务,建议结合平台的告警和日志聚合功能缩短排查周期。


