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 | job.setInputFormatClass(CombineTextInputFormat.class); |
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 | <property> |
2. 启用 Combiner——Map 端提前聚合
Combiner 在 Map 端做局部聚合,减少传给 Reduce 的数据量。注意:操作必须满足结合律(求和、计数可以用,平均值不行)。
1 | job.setCombinerClass(MyReducer.class); |
3. 调大 Reduce 拉取并行度
Reduce 默认 5 个线程同时拉数据,网络带宽够可以调到 10-15:
1 | <property> |
Reduce 阶段:解决数据倾斜
数据倾斜的表现:大部分 Reduce 任务已经完成,一两个任务还在跑。
解决方案:
- 自定义分区器:热点 Key 分散到多个分区,而不是全去一个 Reduce
1 | // 热点 Key 分配到前 3 个分区,其他 Key 正常哈希 |
- 加随机后缀:在 Map 端给热点 Key 加随机数(如
key_0、key_1),分散到不同 Reduce 做局部聚合,再第二次 MapReduce 做全局聚合 - 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 | 1. 作业慢 → YARN UI 看哪个阶段慢 |
每次只改一个参数,改完验证效果。 多个参数一起改,出问题不知道是哪个导致的。