MapReduce原理
文章目录
MapReduce架构
在MapReduce
中,用于执行MapReduce
任务的机器有两个角色:JobTracker
和TaskTracker
。其中JobTracker
是用于调度工作的。TaskTracker
是用于执行工作的。一个Hadoop
集群中只有一台JobTracker
。MapReduce
架构图如下:
客户端向JobTracker
提交一个作业,JobTracker
把这个作业拆分成很多份,然后分配给TaskTracker
去执行,TaskTracker
会隔一段时间向JobTracker
发送心跳信息(Heartbeat
),如果JobTracker
在一段时间内没有收到TaskTracker
的心跳信息,JobTracker
会认为TaskTracker
挂掉了,会把TaskTracker
的作业任务分配给其他TaskTracker
。
MapReduce执行过程
执行过程详细步骤:
- 客户端启动一个
Job
。- 客户端向
JobTracker
请求一个作业号(JobID
)。- 将运行作业所需要的资源文件复制到
HDFS
上,包括MapReduce
程序打包的JAR
文件、配置文件和客户端计算所得的输入划分信息。这些文件都存放在JobTracker
专门为该作业创建的文件夹中,文件夹名为该作业JobID
。JAR
文件默认会有10
个副本,输入划分信息告诉JobTracker
应该为这个作业启动多少个map
任务等信息。JobTracker
接收到作业后将其放在作业队列中,等待JobTracker
对其进行调度。当JobTracker
根据自己的调度算法调度该作业时,会根据输入划分信息为每个划分创建一个map
任务,并将map
任务分配给TaskTracker
执行。这里需要注意的是,map
任务不是随便分配给某个TaskTracker
的,Data-Local
(数据本地化)将map
任务分配给含有该map
处理的数据库的TaskTracker
上,同时将程序JAR
包复制到该TaskTracker
上运行,但是分配reducer
任务时不考虑数据本地化。TaskTracker
每隔一段时间给JobTracker
发送一个Heartbeat
告诉JobTracker
它仍然在运行,同时心跳还携带很多比如map
任务完成的进度等信息。当JobTracker
收到作业的最后一个任务完成信息时,便把作业设置成“成功”,JobClient
再传达信息给用户。
MapReduce的map与reduce过程
MapReduce
流程总览:
Map
端从HDFS
读取一个文件(一个文件在HDFS
有多个块),将这个文件分片,不同的分片进入不同的Map
任务执行操作。再经过Shuffle
阶段进入到Reduce
阶段,最后根据Reduce
任务的个数输入到HDFS
中,有一个Reduce
任务就有一个part-r-0000x
文件,其中Reduce
任务的数量是可以通过job.setNumReduceTasks(int num)
设置的。
shuffle
过程其实是包含在map
和reduce
中的,并不是独立的一个阶段,下下图中红线框住的就是shuffle
过程。
MapReduce
详细流程:
Map
端分析:
- 每个输入分片会让一个
map
任务来处理,默认情况下,以HDFS
的一个块的大小(hadoop 1.x
默认64M
,hadoop 2.x
默认128M
)为一个分片,当然我们也可以设置块的大小。map
任务的结果会先输出到一个环形内存缓冲区(默认大小为100M
,由io.sort.mb
属性控制,缓冲区的作用是批量收集map
结果,减少磁盘IO
的影响),当该缓冲区快要溢出时(默认为缓冲区大小的80%
,由io.sort.spill.percent
属性控制),会在本地文件系统中创建一个溢出文件,将该缓冲区中的数据写入这个文件。- 在写入磁盘之前,线程首先根据
reduce
任务的数目将数据划分为相同数目的分区,也就是一个reduce
任务对应一个分区的数据。这样做是为了避免有些reduce
任务分配到大量数据,而有些reduce
任务却分到很少数据,甚至没有分到数据的尴尬局面。其实分区就是对数据进行hash
的过程。然后对每个分区中的数据进行排序,如果此时设置了Combiner
,将排序后的结果进行Combianer
操作,这样做的目的是让尽可能少的数据写入到磁盘。- 当
map
任务输出最后一个记录时,可能会有很多的溢出文件,这时需要将这些文件合并。合并的过程中会不断地进行排序和combiner
操作,目的有两个:1
、尽量减少每次写入磁盘的数据量;2
、尽量减少下一复制阶段网络传输的数据量。最后合并成了一个已分区且已排序的文件。为了减少网络传输的数据量,这里可以将数据压缩,只要将mapred.compress.map.out
设置为true
就可以。数据压缩:Gzip
、Lzo
、snappy
。- 将合并的文件写入磁盘,告知
reduce
端主动来读取数据。
Shuffle
过程分析:
map
端的shuffle
:
- 每个
map task
都有一个内存缓冲区,存储着map
的输出结果,当缓冲区快满的时候需要将缓冲区的数据以一个临时文件的方式存放到磁盘,当整个map task
结束后在对磁盘中这个map task
产生的所有临时文件做一个合并,生成最终的正式输出文件,然后等待reduce task
来拉数据。- 在
map task
执行时,它的输入数据来源于HDFS
的block
,当然在MapReduce
概念中,map task
只读取split
。split
与block
对应关系可能是多对一,默认是一对一。在wordcount
例子里,假设map
的输入数据都是是像“aaa”
这样的字符串。- 在经过
mapper
的运行后,我们得知mapper
的输出是这样一个key/value
对:key
是“aaa”
,value
是数值1
。因为当前map
端只做加1
的操作,在reduce task
里采取合并结果集。前面我们知道这个job
有3
个reduce task
。那到底当前的“aaa”
究竟该丢给哪个reduce
去处理呢?是需要现在做决定的。MapReduce
提供Partitioner
接口,作用就是根据key
或value
及reduce
的数量来决定当前的输出数据最终应该交由哪个reduce task
处理。默认对key hash
后再以reduce task
数据取模。默认的取模方式只是为了平均reduce
的处理能力,如果用户自己对Partitioner
有需求,可以定制并设置到job
上。- 在例子中,
“aaa”
经过Partition
后返回0
,也就是这对值应当交由第一个reduce
来处理。接下来,需要将数据写入内存缓冲区中,缓冲区的作用是批量收集map
结果,减少磁盘IO
的影响。我们的key/value
对以及Partition
的结果都会被写入缓冲区。当然,写入之前,key
与value
值都会被序列化成字节数组。- 内存缓冲区是有大小限制的,默认是
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
模型默认的行为,这里的排序也是对序列化的字节做的排序。- 因为
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
的最终结果。- 每次溢写会在磁盘上生成一个溢写文件,如果map的输出结果真的很大,有多次这样的溢写发生,磁盘上相应的就会有多个溢写文件存在。当
map task
真正完成时,内存缓冲区中的数据也全部溢写到磁盘中形成一个溢写文件。最终磁盘中会至少有一个这样的溢写文件存在(如果ma
p的输出结果很少,当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
的后半段过程开始启动。
Reduce
端的shuffle
:
copy
过程,简单地拉取数据。Reduce
进程启动一些数据copy
线程(Fetcher)
,通过http
方式请求map task
所在的TaskTracker
获取map task
的输出文件。因为map task
早已结束,这些文件就归TaskTracker
管理在本地磁盘中。Merge
阶段。这里的merge
和map
端的merge
动作相同,只是数组中存放的是不同map
端copy
来的数值。copy
过来的数据会先放入内存缓冲区中,这里的缓冲区大小要比map
端更为灵活,它基于JVM
的heap size
设置,因为Shuffle
阶段Reducer
不运行,所以应该把绝大部分的内存都给Shuffle
使用。Merge
有三种形式:1
、内存到内存;2
、内存到磁盘;3
、磁盘到磁盘。默认情况下第一种形式不启用,让人比较困惑。当内存中的数据量到达一定阈值,就启动内存到磁盘的merge
。与map
端类似,这也是溢写的过程,在这个过程中如果你设置有Combiner
,也是会启用的,然后在磁盘中生成了众多溢写文件。第二种merge
方式一直在运行,直到没有map
端的数据时才结束,然后启动第三种磁盘到磁盘的merge
方式生成最终的那个文件。
Reduce
端分析:
reduce
会接收到不同map
任务传来的数据,并且每个map
传来的数据都是有序的。如果reduce
端接收的数据量相当小,则直接存储在内存中(缓冲区大小由mapred.job.shuffle.input.buffer.percent
属性控制,表示用作此用途的堆空间百分比),如果数据量超过了该缓冲区大小的一定比例(由mapred.job.shuffle.merg.percent
决定),则对数据合并后溢写到磁盘中。- 随着溢写文件的增多,后台线程会将它们合并成一个更大的有序的文件,这样做是为了给后面的合并节省空间。其实不管在
map
端还是在reduce
端,MapReduce
都是反复地执行排序,合并操作,现在终于明白了有些人为什么会说:排序是hadoop
的灵魂。- 合并的过程中会产生许多的中间文件(写入磁盘了),但
MapReduce
会让写入磁盘的数据尽可能地少,并且最后一次合并的结果并没有写入磁盘,而是直接输入到reduce
函数。
*Reducer
的输入文件。不断地merge
后,最后会生成一个“最终文件”。为什么加引号?因为这个文件可能存在于磁盘上,也可能存在于内存中。对我们来说,希望它存放于内存中,直接作为Reducer
的输入,但默认情况下,这个文件是存放于磁盘中的。当Reducer
的输入文件已定,整个Shuffle
才最终结束。然后就是Reducer
执行,把结果放到HDSF
上。- 注意:对
MapReduce
的调优在很大程度上就是对MapReduce Shuffle
的性能的调优。