这是一个非常核心的MapReduce调优问题。设置Map和Reduce任务的数量不是一个固定的数字,而是需要根据数据量、集群资源和业务逻辑来权衡的。
下面我将详细解释如何设置Map和Reduce的数量,以及其背后的依据。
1. Map任务数量的设置
Map任务的数量主要不由用户直接设定,而是由输入数据的大小和分片策略决定的。
核心依据:输入分片
- 概念:MapReduce在处理数据前,会将输入数据逻辑上划分为多个输入分片。每个分片会启动一个Map任务来处理。
- 分片大小:分片大小通常由
mapreduce.input.fileinputformat.split.minsize和mapreduce.input.fileinputformat.split.maxsize参数控制,但最直接的影响参数是mapreduce.input.fileinputformat.split.minsize(默认值为1)和HDFS块大小。 - 默认行为:默认情况下,分片大小等于HDFS的块大小(比如128MB或256MB)。也就是说,一个128MB的文件,如果HDFS块大小是128MB,它会被存储为一个块,并对应一个Map任务。一个1GB的文件(1024MB)在128MB的块大小下,会被分为8个块,因此会启动8个Map任务。
如何控制和调整?
虽然不能直接设置Map任务数,但可以通过调整分片大小来间接控制。
-
想增加Map数量(处理大量小文件时有用):
- 方法:减小分片大小。例如,如果HDFS块大小是128MB,你可以设置最大分片大小为64MB,这样两个分片会对应一个块,Map任务数就会翻倍。
- 命令/参数:
-D mapreduce.input.fileinputformat.split.maxsize=67108864(64MB)
-
想减少Map数量(处理少量超大文件,减少调度开销):
- 方法:增大分片大小。例如,设置最小分片大小为256MB,这样两个128MB的块会被合并到一个分片中,由一个Map任务处理。
- 命令/参数:
-D mapreduce.input.fileinputformat.split.minsize=268435456(256MB)
特殊情况:大量小文件
这是MapReduce的噩梦。如果有10000个1MB的小文件,默认会产生10000个Map任务,巨大的任务启动和调度开销会严重拖慢作业。解决方案通常是:
- 使用CombineFileInputFormat:它可以将多个小文件打包到一个分片中,显著减少Map任务数。
- 预处理:在摄入HDFS前,先将小文件合并成大文件(如SequenceFile)。
总结:Map任务数 ≈ 输入数据总大小 / 分片大小
2. Reduce任务数量的设置
与Map任务不同,Reduce任务的数量完全由用户指定。
设置方法:
- 编程方式:在驱动代码中设置
job.setNumReduceTasks(int n) - 命令行方式:
-D mapreduce.job.reduces=10
核心依据:
设置Reduce数量的依据比Map更复杂,需要在负载均衡、开销、容错之间取得平衡。
-
黄金法则:使每个Reduce任务处理的数据量适中
- 经验值:通常建议每个Reduce任务处理的数据量在1GB以内,比如200MB-800MB是一个比较理想的区间。
- 计算方式:
Reduce任务数 ≈ 期望的Reduce端输出数据总量 / 每个Reduce理想处理量 - 例如:如果Map阶段输出数据总共是100GB,你希望每个Reduce处理500MB,那么Reduce任务数可以设置为
100 * 1024 / 500 ≈ 205。
-
避免极端情况:
- 设置成0:表示没有Reduce阶段,只有Map阶段。适用于只需要过滤、转换,不需要排序和聚合的场景。
- 设置成1:这是最简单的测试方式,但会导致所有数据都被发送到一个节点上进行最终聚合,完全无法利用集群的并行能力,性能极差。
- 设置过多:
- 缺点1:启动开销大。每个Reduce任务都需要初始化、调度,过多的任务会增加整个作业的运行时间。
- 缺点2:产生大量小文件。每个Reduce任务会生成一个独立的输出文件(
part-r-00000,part-r-00001…)。如果Reduce任务数成千上万,会在HDFS上产生大量小文件,给NameNode带来巨大压力,也影响后续作业的处理。
-
利用集群资源
- 一个常见的起点是将Reduce任务数设置为 (集群总Reduce槽位数 * 0.95 ~ 1.75)。
- 0.95:所有任务一结束就能马上进行下一波,充分利用资源,但负载可能不均衡(最后一个完成的任务最慢)。
- 1.75:第一波任务完成后,速度快的节点可以马上开始第二波任务,有利于负载均衡,但调度开销稍大。
- 你可以通过YARN的ResourceManager Web UI查看集群的可用资源。
- 一个常见的起点是将Reduce任务数设置为 (集群总Reduce槽位数 * 0.95 ~ 1.75)。
一个简单的计算示例:
- 总数据:Map阶段输出数据为1TB。
- 集群资源:集群有100个NodeManager,每个节点配置了5个Reduce槽位,总Reduce槽位数为500。
- 计算:
- 根据数据量:假设我们希望每个Reduce处理500MB数据,则
Reduce任务数 = 1024GB / 0.5GB ≈ 2048个。 - 根据集群资源:
500个槽位 * 1.75 = 875个。
- 根据数据量:假设我们希望每个Reduce处理500MB数据,则
- 权衡:2048个任务可能过多(会产生2048个小文件),而875个可能更合适。这时每个Reduce处理的数据量是
1024GB / 875 ≈ 1.17GB,稍微偏大但可以接受。你可以尝试先设置为875,然后根据实际运行情况(GC时间、任务是否OOM)进行微调。
实践建议总结
- Map任务数:优先由输入数据和分片策略自动决定。只有在遇到大量小文件或需要优化时才去调整分片大小。
- Reduce任务数:
- 起点:使用
(节点数 *mapreduce.tasktracker.reduce.tasks.maximum) * (0.95 ~ 1.75)作为初始值。在现代YARN中,更关注总容器资源。 - 基准测试:对一个数据子集进行测试。观察作业日志中每个Reduce任务的处理数据量。
- 监控调整:运行作业后,通过监控界面观察:
- 是否有少数Reduce任务运行时间远长于其他任务(数据倾斜)?这可能需要对Key进行更好的设计。
- 任务是否因内存不足而失败?可能需要减少每个Reduce的数据量(即增加Reduce数)或调整JVM堆大小。
- 最终输出文件数:记住Reduce任务数会直接影响HDFS上的输出文件数量,这关系到后续步骤的效率。
- 起点:使用
没有一劳永逸的最优值,最佳的设置需要通过多次测试和监控来找到最适合你当前数据和集群环境的那个平衡点。

被折叠的 条评论
为什么被折叠?



