0%

MapReduce优化

MapReduce 作业慢?别乱调参数,先找到瓶颈在哪

MapReduce 作业跑得慢,原因可能出在任何地方——CPU、内存、磁盘、网络、数据倾斜、小文件、参数配置……每个环节都可能拖后腿。

最忌讳的是盲调——看着参数列表挨个改,改完跑一遍,慢就再改。这样不但效率低,还可能越调越慢。

正确的做法是:先定位瓶颈,再针对性调优。

先定位:作业慢,卡在哪个阶段?

YARN UI 是定位瓶颈的第一站。看一个作业的进度:

  • Map 阶段慢 → 问题在数据读取或 Map 逻辑
  • Shuffle 阶段慢 → 问题在网络传输或数据量
  • Reduce 阶段慢 → 问题在数据倾斜或聚合逻辑

再看具体指标:

现象 可能的原因 排查方向
Map 任务进度不均 分片大小不均、数据倾斜 检查 InputSplit 分布
Reduce 任务有一个特别慢 热点 Key 数据倾斜 看同一个 Key 的数据量
磁盘 I/O 满(>80%) 溢写太频繁 调大 io.sort.mb
网络流量大 Shuffle 数据量大 开启压缩、启用 Combiner
CPU 高但作业慢 计算逻辑复杂 优化代码、减少不必要的序列化

定位清楚再动手,比盲目调参数有效十倍。

从投入产出比看:哪些优化最值得做

不是所有优化都值得花时间。按”投入小、收益大”排序:

优先级 优化手段 收益 成本
开启 Map 输出压缩(Snappy) Shuffle 数据量减少 50%+ 改一行配置
启用 Combiner Map 输出数据量大幅减少 一行代码
解决数据倾斜 作业时间从几小时降到几十分钟 改分区逻辑或加随机后缀
调大 io.sort.mb 溢写次数减少 50%+ 改一个参数
合并小文件(CombineTextInputFormat) Map 任务数从几千降到几十 改一行代码
调大 Reduce 拉取并行度 拉取阶段提速 改一个参数
调整 Map/Reduce 数量 提升并行度 改一个参数
有钱就干 增加硬件资源(内存、CPU) 立竿见影 加机器/内存

实际经验:先做前三个(压缩 + Combiner + 数据倾斜),能解决 80% 的性能问题。

各阶段的针对性优化

输入阶段:减少 Map 任务数

Map 任务太多,调度开销大。任务数 = 数据量 / 分片大小。

  • 小文件多 → 用 CombineTextInputFormat 合并分片
  • 分片太小 → 调大 split.maxsize,让每个 Map 处理更多数据
  • 推荐分片大小:128MB-256MB,一个 Map 任务处理 1-2 分钟的数据量最合适
1
2
job.setInputFormatClass(CombineTextInputFormat.class);  
CombineTextInputFormat.setMaxInputSplitSize(job, 128 * 1024 * 1024); // 分片最大128MB

Map 阶段:减少溢写次数

Map 输出先写内存缓冲区(100MB),满了溢写到磁盘。溢写是磁盘 I/O,次数越多越慢。

参数 默认值 调优建议
mapreduce.task.io.sort.mb 100MB 内存够用调到 256-512MB
mapreduce.map.sort.spill.percent 0.8(80%) 调到 0.85-0.9,多用点缓冲区

Shuffle 阶段:减少数据传输

Shuffle 是 MapReduce 最耗时的环节,优化空间最大。

1. 开启压缩——收益最高

Map 输出压缩后,网络传输量减少 50%-70%。

1
2
3
4
5
6
7
8
<property>
<name>mapreduce.map.output.compress</name>
<value>true</value>
</property>
<property>
<name>mapreduce.map.output.compress.codec</name>
<value>org.apache.hadoop.io.compress.SnappyCodec</value>
</property>

2. 启用 Combiner——Map 端提前聚合

Combiner 在 Map 端做局部聚合,减少传给 Reduce 的数据量。注意:操作必须满足结合律(求和、计数可以用,平均值不行)。

1
job.setCombinerClass(MyReducer.class);

3. 调大 Reduce 拉取并行度

Reduce 默认 5 个线程同时拉数据,网络带宽够可以调到 10-15:

1
2
3
4
<property>
<name>mapreduce.reduce.shuffle.parallelcopies</name>
<value>10</value>
</property>

Reduce 阶段:解决数据倾斜

数据倾斜的表现:大部分 Reduce 任务已经完成,一两个任务还在跑。

解决方案:

  1. 自定义分区器:热点 Key 分散到多个分区,而不是全去一个 Reduce
1
2
3
4
// 热点 Key 分配到前 3 个分区,其他 Key 正常哈希
if (isHotKey(key)) {
return Math.abs(key.hashCode()) % 3;
}
  1. 加随机后缀:在 Map 端给热点 Key 加随机数(如 key_0key_1),分散到不同 Reduce 做局部聚合,再第二次 MapReduce 做全局聚合
  2. Map 端 Join:如果 Join 时一张表很小,用 Map 端 Join 替代 Reduce 端 Join,省掉 Shuffle

资源参数:给多少内存合适

Map 和 Reduce 任务的内存配置直接影响作业稳定性:

参数 默认值 说明
mapreduce.map.memory.mb 1024MB Map 任务总内存。数据量大时调到 2048-4096
mapreduce.reduce.memory.mb 1024MB Reduce 任务总内存。聚合任务调到 4096-8192
mapreduce.map.java.opts -Xmx820m Map JVM 堆内存,比总内存小 20%
mapreduce.reduce.java.opts -Xmx820m Reduce JVM 堆内存

一个常见的 OOM 场景: 只调大了 io.sort.mb(比如调到 512MB),但没调 mapreduce.map.memory.mb(默认 1024MB),Map 任务内存不够,OOM。

原则:调大缓冲区时,同步调大 Map 总内存。

一个完整的调优流程

1
2
3
4
5
6
7
1. 作业慢 → YARN UI 看哪个阶段慢
2. Map 慢 → 检查分片大小、小文件、CPU 是否打满
3. Shuffle 慢 → 开启压缩 + Combiner
4. Reduce 慢 → 检查数据倾斜
5. 调大缓冲区减少溢写 → 同步调大 Map 内存
6. 调 Reduce 数量 → 大约是 CPU 核心数的 0.7-1 倍
7. 再次运行 → 对比时间

每次只改一个参数,改完验证效果。 多个参数一起改,出问题不知道是哪个导致的。

欢迎关注我的其它发布渠道