您的位置:首页 > 其它

MapReduce实现技术分析+WordCount分析

2016-10-09 16:34 411 查看
之前看了一些MapReduce的文章,单独讲Mapreduce各步骤实现过程和WordCount的比较多。尤其是WordCount程序的分析基本都是粘贴官方的,并不是很详细。

看到 雷子-晓飞爸 的文章 http://www.cnblogs.com/npumenglei/p/3631244.html ,觉得写的非常好,拿来参考一下。



图中没有包含spill,spill是MapReduce利用内存缓冲的方式提高效率, 整个的过程和原理并没有受影响, 所以在此处忽略掉spill 过程, 以便更好理解。
假设我们上传了两个文本文件


File 1 内容:
My name is Tony
My company is pivotal

File 2 内容:
My name is Lisa
My company is EMC


1. 第一步, Map

顾名思义, Map 就是拆解.

首先我们的输入就是两个文件, 默认情况下就是两个split, 对应前面图中的split 0, split 1

两个split 默认会分给两个Mapper来处理, WordCount例子相当地暴力, 这一步里面就是直接把文件内容分解为单词和 1 (注意, 不是具体数量, 就是数字1)其中的单词就是我们的主健,也称为Key, 后面的数字就是对应的值,也称为value.

那么对应两个Mapper的输出就是:

split 0

My          1
name        1
is          1
Tony        1
My          1
company     1
is          1
Pivotal     1


split 1

My          1
name        1
is          1
Lisa        1
My          1
company     1
is          1
EMC         1


2. Partition

Partition 是什么? Partition 就是分区。

为什么要分区? 因为有时候会有多个Reducer, Partition就是提前对输入进行处理, 根据将来的Reducer进行分区. 到时候Reducer处理的时候, 只需要处理分给自己的数据就可以了。

如何分区? 主要的分区方法就是按照Key 的不同,把数据分开,其中很重要的一点就是要保证Key的唯一性, 因为将来做Reduce的时候有可能是在不同的节点上做的, 如果一个Key同时存在于两个节点上, Reduce的结果就会出问题, 所以很常见的Partition方法就是哈希。

结合我们的例子, 我们这里假设有两个Reducer, 前面两个split 做完Partition的结果就会如下:

split 0

Partition 1:
company     1
is          1
is          1

Partition 2:
My          1
My          1
name        1
Pivotal     1
Tony        1


split 1

Partition 1:
company 1
is    1
is      1
EMC   1

Partition 2:
My          1
My          1
name        1
Lisa        1


其中Partition 1 将来是准备给Reducer 1 处理的, Partition 2 是给Reducer 2 的

这里我们可以看到, Partition 只是把所有的条目按照Key 分了一下区, 没有其他任何处理, 每个区里面的Key 都不会出现在另外一个区里面。

3. Sort

Sort 就是排序喽, 其实这个过程在我来看并不是必须的, 完全可以交给客户自己的程序来处理。 那为什么还要排序呢? 可能是写MapReduce的大牛们想,“大部分reduce 程序应该都希望输入的是已经按Key排序好的数据, 如果是这样, 那我们就干脆顺手帮你做掉啦, 请叫我雷锋!” ……好吧, 你是雷锋.

那么我们假设对前面的数据再进行排序, 结果如下:

split 0

Partition 1:
company 1
is      1
is    1

Partition 2:
My     1
My    1
name  1
Pivotal   1
Tony    1


split 1

Partition 1:
company 1
EMC   1
is    1
is      1

Partition 2:
Lisa   1
My     1
My       1
name   1


这里可以看到, 每个partition里面的条目都按照Key的顺序做了排序

4. Combine

什么是Combine呢? Combine 其实可以理解为一个mini Reduce 过程, 它发生在前面Map的输出结果之后, 目的就是在结果送到Reducer之前先对其进行一次计算, 以减少文件的大小, 方便后面的传输。 但这步也不是必须的。

按照前面的输出, 执行Combine:

split 0

Partition 1:
company 1
is      2

Partition 2:
My     2
name  1
Pivotal   1
Tony    1


split1

Partition 1:
company 1
EMC   1
is    2

Partition 2:
Lisa   1
My     2
name   1


我们可以看到, 针对前面的输出结果, 我们已经局部地统计了is 和My的出现频率, 减少了输出文件的大小。

5. Copy

下面就要准备把输出结果传送给Reducer了。 这个阶段被称为Copy, 但事实上雷子认为叫他Download更为合适, 因为实现的时候, 是通过http的方式, 由Reducer节点向各个mapper节点下载属于自己分区的数据。

那么根据前面的Partition, 下载完的结果如下:

Reducer 节点 1 共包含两个文件:

Partition 1:
company 1
is      2


Partition 1:
company  1
EMC    1
is    2


Reducer 节点 2 也是两个文件:

Partition 2:
My     2
name  1
Pivotal   1
Tony    1


Partition 2:
Lisa   1
My     2
name   1


这里可以看到, 通过Copy, 相同Partition 的数据落到了同一个节点上。

6. Merge

如上一步所示, 此时Reducer得到的文件是从不同Mapper那里下载到的, 需要对他们进行合并为一个文件, 所以下面这一步就是Merge, 结果如下:

Reducer 节点 1

company 1
company  1
EMC    1
is      2
is    2


Reducer 节点 2

Lisa  1
My     2
My    2
name  1
name  1
Pivotal   1
Tony    1


7. Reduce

终于可以进行最后的Reduce 啦…这步相当简单喽, 根据每个文件中的内容最后做一次统计, 结果如下:

Reducer 节点 1

company 2
EMC    1
is      4


Reducer 节点 2

Lisa  1
My     4
name  2
Pivotal   1
Tony    1


至此大功告成! 我们成功统计出两个文件里面每个单词的数目, 同时把它们存入到两个输出文件中, 这两个输出文件也就是传说中的 part-r-00000 和 part-r-00001, 看看两个文件的内容, 再回头想想最开始的Partition, 应该是清楚了其中的奥秘吧。

如果你在你自己的环境中运行的WordCount只有part-r-00000一个文件的话, 那应该是因为你使用的是默认设置, 默认一个job只有一个reducer

如果你想设两个, 你可以:

在源代码中加入 job.setNumReduceTasks(2), 设置这个job的Reducer为两个

或者

在 mapred-site.xml 中设置下面参数并重启服务

<property>
<name>mapred.reduce.tasks</name>
<value>2</value>
</property>


这样, 整个集群都会默认使用两个Reducer

以下是参考另一篇文章

http://www.cnblogs.com/ywl925/p/3981360.html

1. MapReduce处理中,数据的流程是什么?

在客户端、JobTracker、TaskTracker的层次来分析MapReduce的工作原理的:  

  a. 在客户端启动一个作业。

  b. 向JobTracker请求一个Job ID。

  c. 将运行作业所需要的资源文件复制到HDFS上,包括MapReduce程序打包的JAR文件、配置文件和客户端计算所得的输入划分信息。这些文件都存放在JobTracker专门为该作业创建的文件夹中。文件夹名为该作业的Job ID。JAR文件默认会有10个副本(mapred.submit.replication属性控制);输入划分信息告诉了JobTracker应该为这个作业启动多少个map任务等信息。

  d. JobTracker接收到作业后,将其放在一个作业队列里,等待作业调度器对其进行调度(这里是不是很像微机中的进程调度呢,呵呵),当作业调度器根据自己的调度算法调度到该作业时,会根据输入划分信息为每个划分创建一个map任务,并将map任务分配给TaskTracker执行。对于map和reduce任务,TaskTracker根据主机核的数量和内存的大小有固定数量的map槽和reduce槽。这里需要强调的是:map任务不是随随便便地分配给某个TaskTracker的,这里有个概念叫:数据本地化(Data-Local)。意思是:将map任务分配给含有该map处理的数据块的TaskTracker上,同时将程序JAR包复制到该TaskTracker上来运行,这叫“运算移动,数据不移动”。而分配reduce任务时并不考虑数据本地化。

  e. TaskTracker每隔一段时间会给JobTracker发送一个心跳,告诉JobTracker它依然在运行,同时心跳中还携带着很多的信息,比如当前map任务完成的进度等信息。当JobTracker收到作业的最后一个任务完成信息时,便把该作业设置成“成功”。当JobClient查询状态时,它将得知任务已完成,便显示一条消息给用户。



如果具体从map端和reduce端分析,可以参考上面的图片,具体如下:

Map端:

  a. 每个输入分片会让一个map任务来处理,默认情况下,以HDFS的一个块的大小(默认为64M)为一个分片,当然我们也可以设置块的大小。map输出的结果会暂且放在一个环形内存缓冲区中(该缓冲区的大小默认为100M,由io.sort.mb属性控制),当该缓冲区快要溢出时(默认为缓冲区大小的80%,由io.sort.spill.percent属性控制),会在本地文件系统中创建一个溢出文件,将该缓冲区中的数据写入这个文件。

  b. 在写入磁盘之前,线程首先根据reduce任务的数目将数据划分为相同数目的分区,也就是一个reduce任务对应一个分区的数据。这样做是为了避免有些reduce任务分配到大量数据,而有些reduce任务却分到很少数据,甚至没有分到数据的尴尬局面。其实分区就是对数据进行hash的过程。然后对每个分区中的数据进行排序,如果此时设置了Combiner,将排序后的结果进行Combia操作,这样做的目的是让尽可能少的数据写入到磁盘。

  c. 当map任务输出最后一个记录时,可能会有很多的溢出文件,这时需要将这些文件合并。合并的过程中会不断地进行排序和combia操作,目的有两个:1.尽量减少每次写入磁盘的数据量;2.尽量减少下一复制阶段网络传输的数据量。最后合并成了一个已分区且已排序的文件。为了减少网络传输的数据量,这里可以将数据压缩,只要将mapred.compress.map.out设置为true就可以了。

  d. 将分区中的数据拷贝给相对应的reduce任务。有人可能会问:分区中的数据怎么知道它对应的reduce是哪个呢?其实map任务一直和其父TaskTracker保持联系,而TaskTracker又一直和JobTracker保持心跳。所以JobTracker中保存了整个集群中的宏观信息。只要reduce任务向JobTracker获取对应的map输出位置就ok了哦。

  到这里,map端就分析完了。那到底什么是Shuffle呢?Shuffle的中文意思是“洗牌”,其实Shuffle也是一个很复杂的过程,这里可以参照下面的第三个问题。

Reduce端:

  a.Reduce会接收到不同map任务传来的数据,并且每个map传来的数据都是有序的。如果reduce端接受的数据量相当小,则直接存储在内存中(缓冲区大小由mapred.job.shuffle.input.buffer.percent属性控制,表示用作此用途的堆空间的百分比),如果数据量超过了该缓冲区大小的一定比例(由mapred.job.shuffle.merge.percent决定),则对数据合并后溢写到磁盘中。

  b.随着溢写文件的增多,后台线程会将它们合并成一个更大的有序的文件,这样做是为了给后面的合并节省时间。其实不管在map端还是reduce端,MapReduce都是反复地执行排序,合并操作,现在终于明白了有些人为什么会说:排序是hadoop的灵魂。

  c.合并的过程中会产生许多的中间文件(写入磁盘了),但MapReduce会让写入磁盘的数据尽可能地少,并且最后一次合并的结果并没有写入磁盘,而是直接输入到reduce函数。

2. Map处理数据后,到Reduce得到数据之前,数据的流程是什么?

其实,将map处理的结果,传输到reduce上的过程,在MapReduce中,可以看做shuffle的过程。

  在map端,每个map task都有一个内存缓冲区,存储着map的输出结果,当缓冲区快满的时候需要将缓冲区的数据以一个临时文件的方式存放到磁盘,当整个map task结束后再对磁盘中这个map task产生的所有临时文件做合并,生成最终的正式输出文件,然后等待reduce task来拉数据。

  a. 在map task执行时,它的输入数据来源于HDFS的block,当然在MapReduce概念中,map task只读取split。Split与block的对应关系可能是多对一,默认是一对一。

  b. 在经过mapper的运行后,我们得知mapper的输出是这样一个key/value对。到底当前的key应该交由哪个reduce去做呢,是需要现在决定的。 MapReduce提供Partitioner接口,它的作用就是根据key或value及reduce的数量来决定当前的这对输出数据最终应该交由哪个reduce task处理。默认对key hash后再以reduce task数量取模。默认的取模方式只是为了平均reduce的处理能力,如果用户自己对Partitioner有需求,可以订制并设置到job上。接下来,需要将数据写入内存缓冲区中,缓冲区的作用是批量收集map结果,减少磁盘IO的影响。我们的key/value对以及Partition的结果都会被写入缓冲区。当然写入之前,key与value值都会被序列化成字节数组。

  c. 这个内存缓冲区是有大小限制的,默认是100MB。当map task的输出结果很多时,就可能会撑爆内存,所以需要在一定条件下将缓冲区中的数据临时写入磁盘,然后重新利用这块缓冲区。这个从内存往磁盘写数据的过程被称为Spill,中文可译为溢写,字面意思很直观。这个溢写是由单独线程来完成,不影响往缓冲区写map结果的线程。溢写线程启动时不应该阻止map的结果输出,所以整个缓冲区有个溢写的比例spill.percent。这个比例默认是0.8,也就是当缓冲区的数据已经达到阈值(buffer size * spill percent = 100MB * 0.8 = 80MB),溢写线程启动,锁定这80MB的内存,执行溢写过程。Map task的输出结果还可以往剩下的20MB内存中写,互不影响。

当溢写线程启动后,需要对这80MB空间内的key做排序(Sort)。排序是MapReduce模型默认的行为,这里的排序也是对序列化的字节做的排序。 存缓冲区没有对将发送到相同reduce端的数据做合并,那么这种合并应该是体现是磁盘文件中的。从官方图上也可以看到写到磁盘中的溢写文件是对不同的reduce端的数值做过合并。所以溢写过程一个很重要的细节在于,如果有很多个key/value对需要发送到某个reduce端去,那么需要将这些key/value值拼接到一块,减少与partition相关的索引记录。

  在map端的过程,可参考下图:



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

简单地说,reduce task在执行之前的工作就是不断地拉取当前job里每个map task的最终结果,然后对从不同地方拉取过来的数据不断地做merge,也最终形成一个文件作为reduce task的输入文件。见下图:



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

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

以上几乎是原文粘了两篇文章,再次注明出处:

http://www.cnblogs.com/npumenglei/p/3631244.html

http://www.cnblogs.com/ywl925/p/3981360.html

感谢两位大神,之后如果有自己的体会,再更新。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  mapreduce