MapReduce 原理

1. mapreduce 的运行机制(Hadoop 2)

首先看下 mapreduce 在 yarn 中的执行流程图

MapReduce 原理_第1张图片

详细流程如下:

  1. client通过API提交一个job

  2. 向资源管理器(ResourceManager)申请作业的id。

  3. 作业客户端端检查作业的输出是否存在,如果存在则报错,检查作业的输入是否存在,如果不存在则报错。并计算输入的分片。将运行作业所需要的资源(包括作业jar文件,配置文件和计算所得的输入分片)复制到HDFS中。

  4. 提交作业

  5. 资源管理器收到提交作业的请求后,将请求转发给调度器(scheduler),调度器分配一个容器,resourcemanager在nodemanager的节点上启动MRAppMaster进程

  6. MRAppMaster将作业初始化

  7. 接收来自共享文件系统的在客户端计算的输入分片,对每一个分片创建一个map任务,以及由mapreduce.job.reduces属性确定的多个reduce对象

  8. MRAppMaster向ResourceManager请求容器运行job

  9. ResourceManager分配了容器之后,MRAppMaster通过与NodeManager通信来启动容器,在NodeManager节点上启动一个task JVM,进程名为YarnChild,任务由主类为YarnChild的java应用程序执行,执行任务之前首先将需要的资源本地化,将作业需要的jar文件等资源拷贝到本地,然后运行map任务或reduce任务

  10. 作业完成后,执行任务清理工作。

2. maptask和reducetask实例数的决定机制

2.1 maptask实例数的决定机制

map 端的分片大小默认和 hdfs 块的大小保持一致,在 hadoop2 中的大小为 128M。即数据有多少个 hdfs 数据块,则就有多少个 map task。

可以自定义切片大小,分配机制定义在FileInputFormat类中,切片主要由这几个值来运算决定

计算切片的算法:

Math.max(minSize, Math.min(maxSize, blockSize));

mapreduce.input.fileinputformat.split.maxsize

参数如果调得比blocksize小,则会让切片变小,而且就等于配置的这个参数的值

mapreduce.input.fileinputformat.split.minsize

参数调的比blockSize大,则可以让切片变得比blocksize还大

2.2 reducetask实例数的决定机制

Reducetask数量的决定是由代码中api来设置:

//默认值是1
job.setNumReduceTasks(4);//设置reducetask为4个

如果数据分布不均匀(针对 hashcode 而言),就有可能在 reduce 阶段产生数据倾斜

注意:reducetask 数量并不是任意设置,还要考虑业务逻辑需求,有些情况下,需要计算全局汇总结果,就只能有 1 个 reducetask。

3. mapreduce 的 shuffle 过程

3.1 主要流程概述

  1. maptask收集我们的map()方法输出的kv对,放到内存缓冲区中(100M)

  2. 从内存缓冲区不断溢出本地磁盘文件(0.8),可能会溢出多个文件

  3. 多个溢出文件会被合并成大的溢出文件

  4. 在溢出过程中,及合并的过程中,都要调用partitoner进行分组和针对key进行排序

  5. reducetask根据自己的分区号,去各个maptask机器上去相应的结果分区数据

  6. reducetask会取到同一个分区的来自不同maptask的结果文件,reducetask会将这些文件再进行合并(归并排序)

  7. 合并成大文件后,shuffle的过程也就结束了,后面进入reducetask的逻辑运算过程(从文件中取出一个一个的键值对group,调用用户自定义的reduce()方法)

Shuffle中的缓冲区大小会影响到mapreduce程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快

缓冲区的大小可以通过参数调整, 参数:io.sort.mb 默认100M

文件最后到达 reducer 端,文件合并的过程是以合并最小数量的文件以便满足最后一趟的合并系数。即每次合并的文件数是不一样的。目的尽量减少写磁盘的数据量,最后一次直接合并到reduce。

3.2 详细流程解释

3.2.1 map 端流程

  1. 每个map task读取自己的split的数据。

  2. 将计算后的结果进行分区,交给不同的reduce端进行处理,默认hash partion。这里假设经过分区后交给第一个reduce进行处理。接下来将map端输出的数据写入内存缓冲区中,目的是批量收集map输出结果,减少磁盘IO的影响。在写入之前, key与value值都会被序列化成字节数组。 (整个内存缓冲区就是一个字节数组)

  3. 由于内存缓冲区是默认大小是100MB。当map task的输出结果很多时,就可能会撑爆内存,所以需要在一定条件下将缓冲区中的数据临时写入磁盘,然后重新利用这块缓冲区。这个从内存往磁盘写数据的过程被称为Spill,中文可译为溢写。这个溢写是由单独线程来完成,不影响往缓冲区写map结果的线程。整个缓冲区有个溢写的比例spill.percent。这个比例默认是0.8,也就是当缓冲区的数据已经达到阈值(buffer size * spill percent = 100MB * 0.8 = 80MB),溢写线程启动,锁定这80MB的内存,执行溢写过程。Map task的输出结果还可以往剩下的20MB内存中写,互不影响。 当溢写线程启动后,需要对这80MB空间内的key做排序(Sort)。排序是MapReduce模型默认的行为,这里的排序也是对序列化的字节做的排序。

在这里我们可以想想,因为map task的输出是需要发送到不同的reduce端去,而内存缓冲区没有对将发送到相同reduce端的数据做合并,那么这种合并应该是体现是磁盘文件中的。从官方图上也可以看到写到磁盘中的溢写文件是对不同的reduce端的数值做过合并。所以溢写过程一个很重要的细节在于,如果有很多个 key/value对需要发送到某个reduce端去,那么需要将这些key/value值拼接到一块,减少与partition相关的索引记录。

在针对每个reduce端而合并数据时,有些数据可能像这样:“aaa”/1, “aaa”/1。对于WordCount例子,就是简单地统计单词出现的次数,如果在同一个map task的结果中有很多个像“aaa”一样出现多次的key,我们就应该把它们的值合并到一块,这个过程叫reduce也叫combine。但 MapReduce的术语中,reduce只指reduce端执行从多个map task取数据做计算的过程。除reduce外,非正式地合并数据只能算做combine了。其实大家知道的,MapReduce中将Combiner等同于Reducer。

如果client设置过Combiner,那么现在就是使用Combiner的时候了。将有相同key的key/value对的value加起来, 减少溢写到磁盘的数据量。Combiner会优化MapReduce的中间结果,所以它在整个模型中会多次使用。那哪些场景才能使用Combiner呢? 从这里分析,Combiner的输出是Reducer的输入,Combiner绝不能改变最终的计算结果。所以从我的想法来看,Combiner只应该用于那种Reduce的输入key/value与输出key/value类型完全一致,且不影响最终结果的场景。比如累加,最大值等。Combiner的使 用一定得慎重,如果用好,它对job执行效率有帮助,反之会影响reduce的最终结果。

  1. 每次溢写会在磁盘上生成一个溢写文件,如果map的输出结果真的很大,有多次这样的溢写发生,磁盘上相应的就会有多个溢写文件存在。当map task真正完成时,内存缓冲区中的数据也全部溢写到磁盘中形成一个溢写文件。最终磁盘中会至少有一个这样的溢写文件存在(如果map的输出结果很少,当 map执行完成时,只会产生一个溢写文件),因为最终的文件只有一个,所以需要将这些溢写文件归并到一起,这个过程就叫做Merge。Merge是怎样的?如前面的例子,“aaa”从某个map task读取过来时值是5,从另外一个map 读取时值是8,因为它们有相同的key,所以得merge成group。什么是group。对于“aaa”就是像这样的:{“aaa”, [5, 8, 2, …]},数组中的值就是从不同溢写文件中读取出来的,然后再把这些值加起来。请注意,因为merge是将多个溢写文件合并到一个文件,所以可能也有相同的key存在,在这个过程中如果client设置过Combiner,也会使用Combiner来合并相同的key。

至此,map端的所有工作都已结束,最终生成的这个文件也存放在TaskTracker够得着的某个本地目录内。每个reduce task不断地通过RPC从JobTracker那里获取map task是否完成的信息,如果reduce task得到通知,获知某台TaskTracker上的map task执行完成,Shuffle的后半段过程开始启动。

3.2.2 reduce 端流程

  1. Copy过程,简单地拉取数据。Reduce进程启动一些数据copy线程(Fetcher),通过HTTP方式请求map task所在的TaskTracker获取map task的输出文件。因为map task早已结束,这些文件就归TaskTracker管理在本地磁盘中。

  2. Merge阶段。这里的merge如map端的merge动作,只是数组中存放的是不同map端copy来的数值。Copy过来的数据会先放入内存缓冲区中,这里的缓冲区大小要比map端的更为灵活,它基于JVM的heap size设置,因为Shuffle阶段Reducer不运行,所以应该把绝大部分的内存都给Shuffle用。这里需要强调的是,merge有三种形式:(1)内存到内存 (2)内存到磁盘 (3)磁盘到磁盘。默认情况下第一种形式不启用。当内存中的数据量到达一定阈值,就启动内存到磁盘的merge。与map端类似,这也是溢写的过程,这个过程中如果你设置有Combiner,也是会启用的,然后在磁盘中生成了众多的溢写文件。第二种merge方式一直在运行,直到没有map端的数据时才结束,然后启动第三种磁盘到磁盘的merge方式生成最终的那个文件。

  3. Reducer的输入文件。不断地merge后,最后会生成一个"最终文件",默认情况下,这个文件是存放于磁盘中的。当Reducer的输入文件已定,整个 Shuffle才最终结束。然后就是Reducer执行,把结果放到HDFS上。

你可能感兴趣的:(MapReduce 原理)