Mapreduce工作流程总结

最近开始回顾,整个mapreduce的工作原理,虽然知道mr是如何工作的,但想想还是将其落地成文字,写成博客比较好,在写的过程中,同时也是重新缕了一下自己的思路,也锻炼了自己的表达能力。

Mapreduce简介

Hadoop MapReduce 源于Google在2004年12月份发表的 MapReduce论文。Hadoop MapReduce 其实就是Google MapReduce的一个克隆版本。Hadoop 2.0即第二代Hadoop系统,其框架最核心的设计是HDFS、MapReduce和YARN。其中,HDFS为海量数据提供存储,MapReduce用于分布式计算,YARN用于进行资源管理。

1、mapreduce工作流程图


流程详解:

1)文件要存储在HDFS中,每个文件被切分成多个一定大小的块也就是Block,(Hadoop1.0默认为64M,Hadoop2.0默认为128M),并且默认3个备份存储在多个的节点中。

2)MR通过Inputformat将数据文件从HDFS中读入取,读取完后会对数据进行split切片,切片的数量根据Block的大小所决定,然后每一个split的个数又决定map的个数,即一个split会分配一个maptask并行实例处理。说到切片下面讲一下切片的小细节:

  • a)获取文件大小 fs.sizeOf(ss.txt);
  • b)计算切片大小  computeSliteSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M
  • c) 默认情况下,切片大小=blocksize
  • d)开始切,形成第 1 个切片: ss.txt—0:128M 2 个切片 ss.txt—128:256M 3个切片 ss.txt—256M:300M(每次切片时,都要判断切完剩下的部分是否大于块的 1.1 倍,不大于 1.1 倍就划分一块切片

例如:件大小是260M

如果一个切片的大小的块是128M,按照我们之前学的是先将260M 分出一个128M,也就是还剩下132M。此时 剩下的132M 里面会在去分128M 和最后一个4M 作为一个独立的Spilt。也就是分了三个块,第一个是128M,第二个是128M,第三个4M,但实际却是这样的:第一次获得块的大小128M,通过一个While(文件的大小/128>1.1)?为真就切一个块为128M的。如果不为真就将剩下的放在一起,作为一个独立的Spilt。比如文件大小260M,每一个块大小分为128M。过程:(260/128=2.03>1.1?) 此时切128M 剩下132M(132/128=1.03>1.1?)此时不大于 所以 将132M 放在一个切片。也就是实际是将260M 分成了两个Spilt 一个是128M 一个是132M

3、切片的时候需要解决记录跨block的问题,这时就需要RecordReader读取,在读取的过程中会保证数据的完整性。例如如果一个句子中的一个单词被切到了不同切片当中,会让数据没法识别,RecordReader读取数据会保证每个单词的完整性。没读取一条记录调用一次map函数

4、数据进入到map函数中,然后开始按照一定的规则切分。比如单词统计,将每一个单词后面计数为1.

5、下面正式进入到了shuffle阶段,也是整个mapreduce最核心的阶段。

  • maptask 收集我们的 map()方法输出的 kv 对,放到内存缓冲区中
  • 从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件
  • 多个溢出文件会被合并成大的溢出文件
  • 在溢出过程中,及合并的过程中,都要调用 partitioner 进行分区和针对 key 进行排
  • reducetask 根据自己的分区号,去各个 maptask 机器上取相应的结果分区数据
  • reducetask 会取到同一个分区的来自不同 maptask 的结果文件, reducetask 会将这些文件再进行合并(归并排序)
  • 合并成大文件后, shuffle 的过程也就结束了,后面进入 reducetask 的逻辑运算过程(从文件中取出一个一个的键值对 group,调用用户自定义的 reduce()方法)

6、最后到reduce,多个reduce任务输入的数据都属于不同的partition,因此结果数据的key不会重复。合并reduce的输出文件即可得到最终结果

细细思考了一下,其实整个mr任务中就是文件不断切分,合并,分区,排序的过程,总结了一下mr中一共有三次排序,第一次是在环形溢写缓冲区中进行快速排序,当达到默认阈值80M时写到spill文件,第二次是在多个spill文件进行merge的过程中排序,第三次是在reduce fetch多个map产生的多个merge文件时做一次sort,整个过程中前一次是快速排序因为在内存中,第二和第三次为归并排序。详细流程图如下:

                

再次全面的讲解这两幅图的内容:

1Read 阶段: Map Task 通过用户编写的 RecordReader, 从输入 InputSplit 中解析出一个个 key/value
2Map 阶段:该节点主要是将解析出的 key/value 交给用户编写 map()函数处理,并产生一系列新的 key/value
3Collect 收集阶段:在用户编写 map()函数中,当数据处理完成后,一般会调用OutputCollector.collect()输出结果。在该函数内部,它会将生成的 key/value 分区(调用Partitioner) , 并写入一个环形内存缓冲区中。
4Spill 阶段:即溢写, 当环形缓冲区满后, MapReduce 会将数据写到本地磁盘上,生成一个临时文件。需要注意的是,将数据写入本地磁盘之前,先要对数据进行一次本地排序,并在必要时对数据进行合并、 压缩等操作。
溢写阶段详情:
步骤
1: 利用快速排序算法对缓存区内的数据进行排序,排序方式是,先按照分区编号partition 进行排序,然后按照 key 进行排序。这样, 经过排序后,数据以分区为单位聚集在一起,且同一分区内所有数据按照 key 有序。
步骤
2: 按照分区编号由小到大依次将每个分区中的数据写入任务工作目录下的临时文件 output/spillN.outN 表示当前溢写次数)中。如果用户设置了 Combiner,则写入文件之前,对每个分区中的数据进行一次聚集操作。
步骤
3: 将分区数据的元信息写到内存索引数据结构 SpillRecord 中,其中每个分区的元信息包括在临时文件中的偏移量、压缩前数据大小和压缩后数据大小。如果当前内存索引大小超过 1MB,则将内存索引写到文件 output/spillN.out.index 中。
5Combine 阶段:当所有数据处理完成后, MapTask 对所有临时文件进行一次合并,以确保最终只会生成一个数据文件。
当所有数据处理完后,
MapTask 会将所有临时文件合并成一个大文件, 并保存到文件output/file.out 中,同时生成相应的索引文件 output/file.out.index。在进行文件合并过程中, MapTask 以分区为单位进行合并。对于某个分区, 它将采用多轮递归合并的方式。 每轮合并 io.sort.factor(默认 100)个文件,并将产生的文件重新加入待合并列表中,对文件排序后,重复以上过程,直到最终得到一个大文件。让每个 MapTask 最终只生成一个数据文件,可避免同时打开大量文件和同时读取大量
小文件产生的随机读取带来的开销。

                 

1Copy 阶段: ReduceTask 从各个 MapTask 上远程拷贝一片数据,并针对某一片数据,如果其大小超过一定阈值,则写到磁盘上,否则直接放到内存中。
2Merge 阶段:在远程拷贝数据的同时, ReduceTask 启动了两个后台线程对内存和磁盘上的文件进行合并,以防止内存使用过多或磁盘上文件过多。
3Sort 阶段:按照 MapReduce 语义,用户编写 reduce()函数输入数据是按 key 进行聚集的一组数据。 为了将 key 相同的数据聚在一起, Hadoop 采用了基于排序的策略。由于各个 MapTask 已经实现对自己的处理结果进行了局部排序,因此, ReduceTask 只需对所有数据进行一次归并排序即可。
4Reduce 阶段: reduce()函数将计算结果写到 HDFS 上。

有不足的地方还望多多指正,后面会继续写一些相关的大数据技术博客


猜你喜欢

转载自blog.csdn.net/u013982921/article/details/80932441