1. 这不是教科书里的抽象概念,而是每天在真实集群里跑起来的“数据流水线”
HDFS和MapReduce这两个词,你可能在面试题里背过,在PPT上见过,在技术分享中听过——但真正搞懂它们怎么协同工作、数据在中间经历了什么、每个环节为什么这样设计,光靠概念记忆根本不行。我带过好几个刚从学校出来的同学,他们能画出MapReduce的四步流程图,也能背出HDFS的NameNode/DataNode分工,可一到线上排查一个慢任务,就卡在“明明输入只有10GB,为什么Shuffle阶段写了80GB磁盘?”或者“明明文件存在,Job却报NoRouteToHost”,连日志往哪看都不知道。这说明:知道“是什么”不等于理解“怎么动”,更不等于掌握“为什么这么动”。这篇内容就是为那些已经看过基础定义、正准备动手搭集群或调优任务的人写的。它不讲Hadoop安装步骤,不列API参数表,而是把一次典型的WordCount任务从客户端提交开始,逐帧拆解——数据怎么切、怎么分发、怎么计算、怎么聚合、怎么落盘、怎么容错。你会看到NameNode不只是个“目录管家”,它在Split生成时就决定了Map并发度;DataNode也不只是“硬盘柜”,它的本地性策略直接影响Shuffle网络开销;Combiner不是可有可无的优化项,而是在数据爆炸前最关键的“第一道过滤阀”。所有解释都基于真实集群日志片段、配置项实测对比、以及我踩过的坑:比如为什么mapreduce.input.fileinputformat.split.minsize设成0反而让小文件任务更慢?为什么yarn.nodemanager.resource.memory-mb配得再大,MapTask内存溢出照样发生?这些细节,文档不会写,但生产环境天天发生。如果你的目标是能独立诊断MR任务延迟、合理分配集群资源、甚至为特定场景定制InputFormat或Partitioner,那接下来的内容,就是你该反复翻看的操作手册。
2. 整体流程设计:为什么必须是“先存后算”,而不是“边存边算”?
2.1 HDFS与MapReduce的耦合本质不是技术绑定,而是架构哲学匹配
很多人误以为HDFS是MapReduce的“配套存储”,其实恰恰相反:MapReduce的设计倒逼出了HDFS的形态。回到2003年Google的MapReduce论文,核心思想是“移动计算比移动数据更便宜”。这句话背后藏着一个残酷现实:当时千兆网络带宽约125MB/s,而一块SATA硬盘顺序读取速度已达60MB/s——如果任务需要处理1TB数据,把数据从存储节点拉到计算节点,网络可能成为瓶颈;但如果让计算逻辑直接跑到存数据的机器上,就能最大化利用本地磁盘IO,规避网络传输。这个前提成立,必须满足两个条件:第一,数据得“知道”自己在哪台机器上;第二,计算框架得“知道”数据在哪,才能把Task调度过去。HDFS正是为解决这两个问题而生:它用NameNode维护全局元数据(文件→Block→DataNode映射),用DataNode定期心跳上报块位置,从而让YARN ResourceManager在分配Container时,能优先选择数据所在节点(NODE_LOCAL),次选同机架(RACK_LOCAL),最后才跨机架(OFF_SWITCH)。这不是简单的“就近调度”,而是整套容错体系的基础——当某个DataNode宕机,NameNode立刻知道哪些Block副本丢失,并触发其他节点上的副本重建;而MapTask若失败,YARN只需在另一台有该Block副本的节点上重跑,无需重新拉数据。这种“存储知情+计算感知”的闭环,才是HDFS与MapReduce深度耦合的底层逻辑。所以,当你看到hdfs dfs -ls /input输出里每个文件后面跟着10.0.1.12:9866, 10.0.1.15:9866, 10.0.1.18:9866这样的地址列表时,别只把它当存储位置,要意识到:这三个IP,就是接下来三个MapTask最可能被调度去执行的地方。
2.2 流程不可简化的六个刚性阶段及其设计意图
一次标准MR作业的生命周期,严格遵循以下六个阶段,跳过任一环节都会导致语义错误或容错失效:
Client端作业准备(Pre-Submission)
这阶段常被忽略,却是性能瓶颈的源头。Client不只打包jar包,还要做三件事:① 调用FileInputFormat.listStatus()扫描输入路径,获取所有文件及大小;② 根据minSize/maxSize/splitSize参数计算Split数量(注意:Split是逻辑切分,不是物理切割);③ 读取每个Split的首选DataNode列表(通过blockLocationsAPI),生成InputSplit对象并序列化进JobConf。这里的关键陷阱是:如果输入路径下有1000个小于128MB的文件,即使总大小仅500MB,也会生成1000个Split,进而启动1000个MapTask——每个Task都有JVM启动开销(平均300ms)、Shuffle缓冲区分配(默认100MB)、GC压力。我曾遇到一个ETL任务,因上游生成了2W个小CSV,Map阶段耗时从2分钟飙升到47分钟,根源就在这一阶段。ResourceManager接收与初始化(RM Initialization)
Client提交的job.xml和jar包上传至HDFS临时目录(如/tmp/hadoop-yarn/staging/$USER/.staging/job_123456789)后,RM才开始解析。它不立即分配资源,而是先校验:① 用户是否有队列权限(YARN Capacity Scheduler的ACL检查);②mapreduce.map.memory.mb是否超过队列最大内存限制;③ 是否设置了mapreduce.job.queuename且队列存在。这阶段失败会直接抛QueueNotFoundException或AccessControlException,而非Task失败——意味着问题出在资源配置,而非代码逻辑。ApplicationMaster启动与资源协商(AM Lifecycle)
RM为该作业启动一个专属AM进程(本质是YARN Container),AM启动后立刻向RM申请资源。注意:AM申请的是“容器规格”,不是“具体机器”。例如,它说:“我要2个Map容器,每个需2G内存+1核CPU”,RM返回的是“已分配Container ID: container_123 on host102:8041”。AM拿到后,才通过NodeManager的RPC接口,在host102上拉起MapTask JVM。这个解耦设计让AM能动态调整并发度——比如发现某Map运行超时,AM可额外申请一个Container重跑,而不影响其他Task。MapTask执行与Spill(Map Execution & Spill)
MapTask的核心动作是:读取Split对应的数据块 → 解析成<key,value>对 → 执行map()函数 → 输出结果暂存内存环形缓冲区(默认100MB)。当缓冲区达80%阈值(io.sort.spill.percent=0.8),触发Spill:内存数据按partition排序,写入本地磁盘(yarn.nodemanager.local-dirs指定路径),生成spill0.out。这里的关键是:Spill文件不是最终输出,而是中间态。每个Spill文件包含三部分:索引(记录各partition起始偏移)、元数据(partition数、combiner类名)、实际数据(<key,value>序列化流)。如果启用了Combiner,它会在Spill前对同一partition内相同key的value做预聚合,大幅减少磁盘写入量。实测一个日志统计任务,启用Combiner后Spill大小从12GB降至1.8GB。Shuffle与Sort(Reduce Side Shuffle)
ReduceTask启动后,并不等所有Map完成才开始行动。它采用“拉取模式”:一旦有MapTask完成(状态变为SUCCEEDED),Reduce就通过HTTP连接其所在NodeManager的mapreduce.shuffle.port(默认13562),分片拉取该Map输出的对应partition数据。拉取过程有三重保障:① 每次HTTP请求带MD5校验码,防网络丢包;② 拉取的spill文件被合并成fetcher_001.out存本地;③ 当本地磁盘空间不足(yarn.nodemanager.disk-health-checker.max-disk-utilization-per-disk-percentage默认90%),自动清理旧spill。真正的Sort发生在Reduce端内存:所有拉取的partition数据在内存中归并排序(Merge Sort),形成全局有序的<key,values[]>序列。注意:这个排序是ReduceTask的强制行为,无法关闭——因为后续的reduce()函数要求相同key的所有value必须连续出现。Reduce执行与OutputCommit(Final Output)
排序完成后,ReduceTask遍历有序序列,对每个key调用reduce()函数。输出写入HDFS前,经历两次Commit:① TaskAttemptCommit:ReduceTask将结果写到临时路径(如/output/_temporary/0/_attempt_123_0001_m_000001_0/part-r-00001);② JobCommit:所有Task成功后,AM调用OutputCommitter.commitJob(),将临时目录原子性地重命名为最终路径(/output/part-r-00001)。这个两阶段提交保证了“要么全成功,要么全失败”,避免出现部分文件写入成功、部分失败导致数据不一致。
2.3 为什么不能把MapReduce改成流式处理?——批处理基因决定的硬约束
有人问:“既然Flink能实时处理,为什么Hadoop不改造成流式?”答案藏在上述流程里。MapReduce的每个阶段都依赖全局状态可见性:NameNode必须掌握所有Block位置才能做本地性调度;AM必须等所有MapTask报告完成,才能启动Reduce的Shuffle;Reduce必须收集完所有Map的对应partition,才能开始归并排序。这种强协调机制,天然排斥低延迟。举个例子:如果允许Map边计算边推送结果给Reduce,那么当第1个Map输出key="apple"时,Reduce立刻开始处理,但第5个Map可能也输出了key="apple",此时Reduce已输出结果,无法追加。而批处理通过“先全部计算,再统一聚合”,规避了状态一致性难题。所以,Hadoop生态后来分化出Storm(微批)、Spark(DAG内存计算)、Flink(真流式),不是因为MapReduce“落后”,而是因为不同场景需要不同的状态管理代价——你要吞吐量和容错,就选MapReduce;你要秒级响应,就得接受状态检查点(Checkpoint)带来的额外开销。
3. 核心环节深度解析:从Split生成到OutputCommit的每一步实操细节
3.1 Split生成:小文件与大文件的截然相反优化路径
Split的生成逻辑由FileInputFormat.getSplits()控制,其核心公式为:
splitSize = max(minSize, min(maxSize, blockSize))其中blockSize是HDFS文件的实际块大小(默认128MB),minSize和maxSize由配置项决定:
mapreduce.input.fileinputformat.split.minsize(默认1B)mapreduce.input.fileinputformat.split.maxsize(默认Long.MAX_VALUE)
小文件场景(单文件<<128MB):
假设输入目录有500个1MB的JSON文件,minSize=1,则每个文件生成1个Split,共500个MapTask。优化方案是合并小文件:
- 使用
CombineFileInputFormat(继承自FileInputFormat),它会把多个小文件打包进1个Split; - 设置
mapreduce.input.fileinputformat.split.minsize.per.node=10000000(10MB),表示每个节点至少凑够10MB才生成Split; - 同时设
mapreduce.input.fileinputformat.split.minsize.per.rack=20000000(20MB),确保跨机架时更激进合并。
实测效果:500个1MB文件,原500个MapTask → 合并后仅25个MapTask,JVM启动开销降低95%。
大文件场景(单文件>>128MB):
比如一个2GB的压缩日志文件(gzip格式),由于gzip不支持分片(seek不到中间位置),整个文件只能作为1个Split,由1个MapTask处理。此时splitSize再大也没用。解决方案是:
- 预处理转成支持分片的格式:用
SequenceFile或Parquet重写数据; - 或使用
NLineInputFormat,按行数切分(如每10000行1个Split),牺牲局部性换取并发度; - 最彻底的是改用
TextInputFormat的子类,重写isSplitable()返回true,并实现自定义createRecordReader()解析gzip流。
我曾处理一个30GB gzip日志,改用Parquet后,Map并发度从1提升到24,运行时间从3小时缩短至12分钟。
提示:判断文件是否可分片,执行
hdfs fsck /path/to/file -files -blocks -locations,若输出中isSplittable: true则支持分片,否则需预处理。
3.2 MapTask内存管理:Spill阈值与GC的生死平衡
MapTask的内存布局如下(以mapreduce.map.memory.mb=2048为例):
- JVM堆内存:2048MB
- 环形缓冲区(
io.sort.mb):默认100MB(占堆5%) - 剩余堆内存:用于map()函数逻辑、GC、异常栈
关键参数关系:
io.sort.mb ≤ mapreduce.map.memory.mb × 0.8 # 防止OOM io.sort.spill.percent = 0.8 # 缓冲区80%触发SpillSpill性能瓶颈分析:
当io.sort.mb设为100MB,但map()函数产生大量中间对象(如String拼接、List扩容),实际可用内存可能只剩60MB。一旦达到80%阈值(48MB),Spill立即触发,但此时磁盘IO跟不上内存生成速度,Task线程阻塞等待Spill完成,CPU利用率骤降。解决方案:
- 调高
io.sort.mb:设为512MB(需同步调高mapreduce.map.java.opts=-Xmx1600m,留400MB给GC); - 优化map()逻辑:避免创建临时对象,用
StringBuilder替代+字符串拼接,用PrimitiveCollections替代ArrayList<Integer>; - 启用LZO压缩:设置
mapreduce.map.output.compress=true+mapreduce.map.output.compress.codec=org.apache.hadoop.io.compress.LzopCodec,Spill时压缩率可达3:1,减少磁盘写入量。
实测一个文本解析任务,io.sort.mb从100MB→512MB,Spill次数从12次降至2次,Map阶段耗时下降37%。
3.3 Shuffle网络调优:从TCP参数到HTTP连接池的全链路控制
Reduce端Shuffle性能取决于三个层面:
① 网络层(TCP):
net.ipv4.tcp_window_scaling=1(启用窗口缩放,支持>64KB窗口)net.core.rmem_max=16777216(接收缓冲区16MB)net.core.wmem_max=16777216(发送缓冲区16MB)
这些需在所有DataNode/NodeManager节点的/etc/sysctl.conf中配置并sysctl -p生效。
② YARN层(Container通信):
mapreduce.reduce.shuffle.parallelcopies=20(默认5,提高并发拉取数)mapreduce.shuffle.max.thread.count=100(Shuffle Handler线程池上限)mapreduce.reduce.shuffle.input.buffer.percent=0.7(Reduce端内存70%用于缓存拉取数据)
③ HTTP层(Map端服务):
NodeManager内置Jetty服务器提供Shuffle服务,关键参数:
mapreduce.shuffle.port=13562(确保防火墙开放)mapreduce.shuffle.max.connections=200(单NodeManager最大连接数)mapreduce.shuffle.max.threads=100(Jetty线程池大小)
实测调优案例:
集群100节点,Reduce需拉取200GB数据。原配置(parallelcopies=5)下,Shuffle耗时42分钟,网络IO利用率仅35%。调优后:
- parallelcopies=20
- rmem_max/wmem_max=16MB
- shuffle.max.connections=500
Shuffle耗时降至11分钟,网络IO峰值达92%,证明瓶颈已从网络转向磁盘IO(此时应增加yarn.nodemanager.local-dirs挂载点,分散磁盘压力)。
3.4 Reduce端Sort与Merge:内存与磁盘的动态博弈
ReduceTask的Sort阶段内存使用公式:
sortMemory = mapreduce.reduce.merge.inmem.threshold × io.sort.factor其中:
mapreduce.reduce.merge.inmem.threshold:内存中最多缓存多少个spill文件(默认1000)io.sort.factor:一次归并的spill文件数(默认10)
当拉取的spill文件数超过inmem.threshold,触发In-Memory Merge:将内存中10个spill文件归并成1个,写入磁盘,释放内存。若磁盘空间不足,则触发On-Disk Merge:将磁盘上10个spill文件归并成1个。后者I/O开销极大。
优化策略:
- 增大
io.sort.factor:从10→20,减少归并轮次(但需更多内存); - 调高
mapreduce.reduce.shuffle.input.buffer.percent:从0.7→0.85,增加内存缓冲区; - 启用
mapreduce.reduce.input.buffer.percent=0.0:禁用Reduce端输入缓冲,强制数据直写磁盘,避免内存溢出(适用于超大数据量)。
我处理一个电商订单分析任务(Reduce输入1.2TB),采用策略3后,OOM错误消失,但Shuffle耗时增加18%——这是用时间换稳定性的典型权衡。
3.5 OutputCommit机制:如何避免“半成品”文件污染HDFS
FileOutputCommitter的commit流程分三级:
- TaskAttemptCommit:每个Map/Reduce Task成功后,将输出写入
_temporary/$jobId/_attempt_$taskId; - TaskCommit:TaskAttempt成功后,重命名至
_temporary/$jobId/$taskId; - JobCommit:所有TaskCommit成功后,AM将
_temporary/$jobId整体重命名为最终输出路径。
风险点与防护:
- 问题:若JobCommit失败(如AM崩溃),
_temporary目录残留,下次运行可能因Output directory already exists报错; - 方案:启用
mapreduce.fileoutputcommitter.algorithm.version=2(Hadoop 2.7+),该版本在TaskAttemptCommit时直接写入最终路径的临时子目录(如/output/_temporary/0/_attempt_123_0001_m_000001_0),JobCommit仅做空操作,彻底规避残留问题; - 验证:提交任务后,执行
hdfs dfs -ls /output/_temporary,若看到_temporary/0/下有_attempt_*目录,说明version=1;若为空,说明version=2已生效。
注意:version=2不兼容某些旧版Hive,切换前需测试SQL查询是否正常。
4. 实操全流程演示:以WordCount为例的逐帧日志分析
4.1 客户端提交与Split生成(日志溯源)
执行命令:
hadoop jar hadoop-mapreduce-examples.jar wordcount \ -D mapreduce.input.fileinputformat.split.minsize=67108864 \ # 64MB -D mapreduce.map.memory.mb=2048 \ /input /output关键日志片段(Client端):
INFO input.FileInputFormat: Total input paths to process : 1 INFO input.FileInputFormat: Scanning filesystem for files matching pattern file:/input/*.txt INFO input.FileInputFormat: Total input paths to process : 1 INFO input.FileInputFormat: Total size: 1073741824 (1GB) INFO input.FileInputFormat: Number of splits:3 INFO input.FileInputFormat: Split[0] -> hdfs://namenode:9000/input/data.txt:0+357913941 (341MB) [10.0.1.12, 10.0.1.15] INFO input.FileInputFormat: Split[1] -> hdfs://namenode:9000/input/data.txt:357913941+357913941 (341MB) [10.0.1.18, 10.0.1.22] INFO input.FileInputFormat: Split[2] -> hdfs://namenode:9000/input/data.txt:715827882+357913942 (341MB) [10.0.1.25, 10.0.1.28]分析:
- 文件1GB,
minSize=64MB,blockSize=128MB,计算得splitSize=max(64MB,128MB)=128MB,但因文件未对齐128MB倍数,最终切为3个约341MB的Split; - 每个Split标注了两个DataNode IP,即该Block的两个副本位置,后续MapTask将优先调度到这两台机器。
4.2 MapTask执行与Spill(NodeManager日志解读)
登录10.0.1.12节点,查看NodeManager日志:
INFO mapred.MapTask: Processing split: hdfs://namenode:9000/input/data.txt:0+357913941 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 0 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 1 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 2 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 3 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 4 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 5 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 6 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 7 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 8 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 9 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 10 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 11 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 12 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 13 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 14 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 15 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 16 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 17 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 18 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 19 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 20 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 21 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 22 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 23 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 24 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 25 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 26 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 27 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 28 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 29 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 30 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 31 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 32 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 33 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 34 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 35 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 36 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 37 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 38 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 39 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 40 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 41 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 42 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 43 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 44 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 45 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 46 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 47 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 48 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 49 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 50 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 51 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 52 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 53 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 54 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 55 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 56 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 57 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 58 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 59 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 60 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 61 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 62 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full = 80% INFO mapred.MapTask: Finished spill 63 INFO mapred.MapTask: Starting flush of map output INFO mapred.MapTask: Spilling map output: buffer full =