一. MapTask工作机制
(0)提交Job阶段
步骤4之前为Job的提交流程,Inputformat组件通过getSplits方法对文件进行切片得到splits,有多少个Splits就启动多少MapTask。(切片对应MapTask ,分区对应ReduceTask)
(1)Read阶段
客户端向Yarn提交信息,Yarn开启Mrappmaster
在MrAppMaster启动后,读取切片信息,开启对应的MapTask(切片个数对应MapTask的数量),才是真正的MapTask过程 。
开启MapTask之后通过InpuFormat的recordReader读取数据,默认的为TextInputFormat,由recordReader来读取数据
(2)Map阶段
recordReader读完之后将K V返回给Mapper类,进入Map的业务逻辑 并产生新的K V。
Map逻辑完了之后,通过context.write进行collect收集,默认再使用HashPartitioner进行分区处理。
(3)Collect阶段 (分区+排序)
数据输出到outputCollector环形缓冲区。环形缓冲区的作用是为了批量收集Map的结果,减少磁盘IO影响。
环形缓冲区默认大小100M
①从map结果输出到缓冲区
②将缓冲区的数据临时写入次磁盘 是分开的两个进程,溢写线程启动时,不会阻止map的结果输出。
当缓冲区的数据达到80%的时候,溢写启动,Map的输出结果还可以往剩下的20MB内存中写,互不影响。
或者数据没有到达环形缓冲区的80%,在数据全部读完之后,也可以触发溢写。
里面的数据都按照分区的方式进行存储,存储完到达一定阈值之后开始溢写,【溢写之前】进行快速排序
(4)溢写阶段
往文件溢写
步骤 1:利用快速排序算法对缓存区内的数据进行排序,排序方式是,先按照分区编号Partition 进行排序,然后按照 key 进行排序。这样,经过排序后,数据以分区为单位聚集在一起,且同一分区内所有数据按照 key 有序。
步骤 2:按照分区编号由小到大依次将每个分区中的数据写入任务工作目录下的临时文件 output/spillN.out(N 表示当前溢写次数)中。
如果用户设置了 Combiner,则写入文件之前,对每个分区中的数据进行一次预聚合操作。
步骤 3:将分区数据的元信息写到内存索引数据结构 SpillRecord 中,其中每个分区的元信息包括在临时文件中的偏移量、压缩前数据大小和压缩后数据大小。如果当前内存索引大小超过 1MB,则将内存索引写到文件 output/spillN.out.index 中。
(5)Merge阶段
【溢写完毕】,会产生多个溢写文件。对这些溢写文件进行归并排序,最终输出文件只有一个。
当所有数据处理完成后,MapTask 对所有临时文件进行一次合并,以确保最终只会生成一个数据文件。
当所有数据处理完后,MapTask 会将所有临时文件合并成一个大文件,并保存到文件output/file.out 中,同时生成相应的索引文件 output/file.out.index。
在进行文件合并过程中,MapTask 以分区为单位进行合并。对于某个分区,它将采用多轮递归合并的方式。每轮合并 mapreduce.task.io.sort.factor(默认 10)个文件,并将产生的文件重新加入待合并列表中,对文件排序后,重复以上过程,直到最终得到一个大文件。让每个 MapTask 最终只生成一个数据文件,可避免同时打开大量文件和同时读取大量小文件产生的随机读取带来的开销。
二.ReduceTask工作机制
(1)Copy 阶段:
ReduceTask 从各个 MapTask 上由 Fetcher 线程通过 HTTP 方式远程拉取指定分区的一片数据,并针对某一片数据,如果其大小超过一定阈值,则写到磁盘上,否则直接放到内存中。(分区对应reduce)
(2)(merge+)Sort 阶段:
在远程拷贝数据的同时,ReduceTask 启动了两个后台线程对内存和磁盘上的文件进行合并,以防止内存使用过多或磁盘上文件过多。
按照 MapReduce 语义,用户编写 reduce()函数输入数据是按 key 进行聚集的一组数据。
为了将 key 相同的数据进入同一个reduce,Hadoop 采用了基于排序的策略。由于各个 MapTask 已经实现对自己的处理结果进行了局部排序,因此,ReduceTask 只需对所有数据进行一次归并排序即可。
(3)Reduce 阶段:
对排序后的键值对使用reduce方法,K相同的键值对进入到同一个reduce 方法进行业务逻辑的运行,每次调用会产生零个或者多个键值对。
reduce()函数将计算结果写到 HDFS 上。