一、Spark 任务卡在 99%

Spark 是基于内存的 DAG 引擎,任务卡在 99% 通常表现为 某个 Stage 中的个别 Task 永远跑不完,或者 最后一个小 Stage 一直无法提交

1. 典型现象

  • Spark UI 表现:在 Stages 页面,某个 Stage 的 Tasks 进度条中,绝大多数 Task 显示绿色(成功),但有 1~2 个 Task 长期显示黄色(Running)。
  • 日志表现:Driver 日志没有报错,但 Executor 日志中可能频繁出现 GCTask Killed

2. 核心原因与排查

原因

排查方法

关键指标/日志

数据倾斜

查看卡住 Task 的 Input Read 数据量,是否远大于其他 Task。

Spark UI -> Stages -> Task 列表 -> Input Read / Shuffle Read

频繁 Full GC

查看 Executor 的 JVM Heap 使用率,是否长期接近 100%。

Spark UI -> Executors -> GC Time / Memory Used

推测执行干扰

查看是否有大量 Speculative Tasks 在运行,占用资源。

Spark UI -> Stages -> Speculative Tasks 计数

数据热点 Key

某个 Key 导致 Join 或 GroupBy 单点处理过慢。

检查代码中的 groupBy / join 字段

外部 IO 阻塞

读写 HBase、MySQL、Kafka 时网络阻塞或连接池满。

Executor 日志中的 Connection TimeoutSocketException

3. 针对性解决方案

A. 解决数据倾斜(最常见)

  1. 开启倾斜优化参数
    spark.sql.adaptive.skewJoin.enabled=true  // Spark 3.0+ 自适应查询执行
    
    spark.sql.autoBroadcastJoinThreshold=10MB // 强制小表广播
  2. 代码层面加盐(Salting): 给热点 Key 添加随机前缀,打散数据,聚合后再去掉前缀。
  3. 使用 Hint 提示
    SELECT /*+ BROADCAST(small_table) */ * FROM large_table JOIN small_table ON ...

B. 优化资源与 GC

  1. 调整内存比例
    --conf spark.memory.fraction=0.6  # 降低执行内存比例,增加存储内存
    
    --conf spark.memory.storageFraction=0.5
  2. 增加并行度
    spark.sql.shuffle.partitions=2000  # 默认 200 可能太小,导致单 Task 数据过多

C. 关闭推测执行(如果资源紧张)

推测执行会启动备用 Task,可能加剧资源竞争,导致原 Task 更慢。

spark.speculation=false

二、MapReduce (Hive on MR / Hadoop MR) 任务卡在 99%

MR 是基于磁盘的批处理引擎,任务卡在 99% 通常表现为 Reduce 阶段进度条卡在 99%,因为 个别 Reducer 处理的数据量过大

1. 典型现象

  • YARN/UI 表现:Map 100% 完成,Reduce 进度条长期停留在 99%(例如 99.1%)。
  • 任务计数器:查看 Reduce Input Records,发现某个 Reducer 的输入记录数远超平均值。

2. 核心原因与排查

原因

排查方法

关键指标/日志

Reduce 数据倾斜

在 MR UI 上点击 Reducer 列表,查看每个 Reducer 的 Input Bytes

YARN UI -> Application -> Counters / Reducer Progress

小文件过多

Map 任务数量过多,启动开销大,或输出小文件导致下游慢。

Number of maps 是否异常大

Combiner 未生效

Shuffle 阶段数据量未减少,导致 Reducer 压力大。

检查代码是否设置 Combiner

单节点磁盘故障

某个 NodeManager 磁盘 IO 慢,拖慢该节点上的 Reducer。

YARN NodeManager 日志 / 磁盘监控

内存溢出 (OOM)

Reducer 堆内存不足,频繁 GC 或 Crash 重启。

java.lang.OutOfMemoryError: Java heap space

3. 针对性解决方案

A. 解决数据倾斜(Hive 参数调优)

Hive 提供了专门的参数来自动处理倾斜:

# 开启数据倾斜优化

set hive.groupby.skewindata=true;

# 调整 Reducer 数量(避免默认 1 个或过多)

set mapreduce.job.reduces=200; 

# 或根据输入大小自动计算

set hive.exec.reducers.bytes.per.reducer=256000000; 

set hive.exec.reducers.max=1000;

B. 优化 Map/Reduce 内存

# 增加 Map/Reduce 容器内存

set mapreduce.map.memory.mb=4096;

set mapreduce.reduce.memory.mb=8192;

# 增加 Java Heap 比例

set mapreduce.map.java.opts=-Xmx3200m;

set mapreduce.reduce.java.opts=-Xmx6500m;

C. 启用 Combiner

在 Shuffle 之前先在 Map 端进行局部聚合,减少网络传输和 Reducer 压力。

// 代码中设置

job.setCombinerClass(SumCombiner.class);

// 或者 Hive SQL 中通常自动优化,但需检查配置

set hive.map.aggr=true;

D. 处理小文件

# 合并输入小文件

set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;

# 合并输出小文件

set hive.merge.mapfiles=true;

set hive.merge.mapredfiles=true;

三、Spark vs MR 核心差异对比表

特性

Spark 任务卡 99%

MapReduce 任务卡 99%

卡住位置

通常是最后一个 Stage 中的个别 Task

通常是 Reduce 阶段的个别 Reducer

根本原因

内存计算导致 GC 频繁,或 Shuffle 分区不均

磁盘 IO 瓶颈,或 Key 分布不均导致单 Reducer 过载

UI 排查

Spark UI (4040) -> Stages -> Task Duration

YARN UI -> Application -> Reducer Progress

倾斜解决

spark.sql.adaptive.enabled, 广播 Join, 加盐

hive.groupby.skewindata=true, 调整 Reducer 数

资源调整

Executor 内存/核数,spark.shuffle.partitions

Container 内存 (mapreduce.*.memory.mb)

推测执行

spark.speculation (常需关闭)

mapreduce.map.speculative (常保持开启)

日志获取

Spark UI 直接下载 Executor 日志

yarn logs -applicationId <id>


四、快速决策指南

  1. 先确认引擎
    • 如果是 spark-submitSparkSQL → 走 Spark 排查流程
    • 如果是 hive -e (且配置为 MR 引擎) 或 hadoop jar → 走 MR 排查流程
  2. 看进度条细节
    • Spark:点进卡住的 Stage,看 Task 列表,是不是有一个 Task 时间特别长?
    • MR:点进 Reduce 任务列表,是不是有一个 Reducer 输入数据量特别大?
  3. 第一刀怎么切
    • Spark:先开 spark.sql.adaptive.enabled=true (Spark 3.x),不行再调 shuffle.partitions
    • MR:先开 hive.groupby.skewindata=true,不行再调 mapreduce.job.reduces

更多推荐