一、结构
完成一个MapReduce过程需要以下几步,input、map、combine(可选)、shuffle、reduce和output。其中shuffle居于核心的位置。下面逐一来看这些过程。
二、input & map 源码分析
使用HDFS上的文件作为MapReduce的输入。首先会用org.apache.hadoop.mapreduce.Input
Format类的子类FileInputFormat类将作为输入的HDFS上的文件切分成输入分片(InputSplit),每个InputSplit将作为一个Map任务的输入,再将InputSplit解析为键值对。
InputSplit只是逻辑上对输入数据进行分片,并不是将文件在磁盘上真正的进行切片。InputSplit大小的计算方法查看源码后:
m
a
x
(
m
a
p
r
e
d
.
m
i
n
.
s
p
l
i
t
.
s
i
z
e
,
M
a
t
h
.
m
i
n
(
m
a
p
r
e
d
.
m
a
x
.
s
p
l
i
t
.
s
i
z
e
,
d
f
.
b
l
o
c
k
.
s
i
z
e
)
)
max(mapred.min.split.size,Math.min(mapred.max.split.size,df.block.size))
max(mapred.min.split.size,Math.min(mapred.max.split.size,df.block.size))
得到可以动态的指定Split的大小,但是df.block.size大小一般是128MB。
前文中提到,
I
n
p
u
t
S
p
l
i
t
InputSplit
InputSplit进行的是逻辑切片。既然可以动态指定并且是个逻辑切片就会出现在真正读数据的时候把原有的一段数据切分开。比如一行数据被分割到了两个split中(s1和s2)中,在读取s1中的最后一行的数据时,会一直读取到s2的第一个换行符,而在处理s2时,则要将已经读取的数据跳过以避免重复读取。经过切片后就得到了很多个map,所以map的数量与数据大小和切片的直接相关。当然,一般情况下将切片的大小确定成block块大小。因为如果切片切片容量大于块的容量,Map认为就必须从其他节点读取一部分数据,这样就不能实现完全数据本地性。
当输入文件且分为
I
n
p
u
t
S
p
l
i
t
InputSplit
InputSplit后,由
c
r
e
a
t
e
R
e
c
o
r
d
R
e
a
d
e
r
createRecordReader
createRecordReader方法将
I
n
p
u
t
S
p
l
i
t
InputSplit
InputSplit解析为键值对。这里的键值对为<id,value>的形式,id表示该split的起始的逻辑下标,value表示为该切片的内容。
举例来说:下图所示,经过map后输入的切片数据变为K/V结构,K表示每一个值,V表示每一个值的数量。这里要注意经过map后不会把相同的K的V进行统计,统计是reduce的活。
三、combine
这个过程是可以选择的,可以通过编程自己实现它。这里我们直接来分析一下它是干啥的,其实它可以实现跟reduce相同的功能。如图所示,有了combine后在map端就把一些重复的K的数量给统计了。
四、shuffle
shuffle被称为MR的心脏说明是十分重要的!我们现在来分析一下。
每个Map对应内存中的一个环形缓冲区,缓冲区的大小一般是100M。当缓冲区的容量达到一个阈值时(80%),数据开始溢写到磁盘上。这个时候数据不是直接写入而是要经过以下操作:
将数据分区,分区的数目与Reduce的数据直接相关,因为输入到Reducer中时是将对应分区的数据输入的Reducer中。计算分区的方法可以自己指定也可以按照默认的算法来,这里就不列出这个公式了。感兴趣的童鞋可以去查源码分析一下。
经过分区后就会产生一个小文件,这个小文件内部结构就是按照分区号依次排列的几个小区间。这几个小区间是怎么排列的呢?这里就是说的sort了,首先按照分区号进行排序,然后在每一个分区号内部按照K2规则(或者自定义的规则)继续排序,这样就行了按照经过分区排序的内部有序小文件。那么一个map经过多次溢写后就会产生多个这样的小文件。最终将这些形成的小文件再统一合成一个大文件,合并的方法是按照分区合并,并排序。这样一个map生成一个分区且排序的大文件。
如果这个环形缓冲区写满了,就会发生map阻塞。直到缓冲区的数据被写完了,重新唤醒map。
五、reducer
reducer去map端根据号取数据。比如有多个map输出多个分区1的文件,这个时候首先将属于分区1的小文件合并成大文件并进行内部排序(shuffle),将这个整合多个map对应区的大文件输出给reduce。
六、MapReduce如何组织工作
Hadoop 划分工作为任务。有两种类型的任务:
- Map 任务 (分割及映射)
- Reduce 任务 (重排,还原)
如上所述 完整的执行流程(执行 Map 和 Reduce 任务)是由两种类型的实体的控制,称为
- Jobtracker : 就像一个主(负责提交的作业完全执行)
- 多任务跟踪器 : 充当角色就像从机,它们每个执行工作
对于每一项工作提交执行在系统中,有一个 JobTracker 驻留在 Namenode 和 Datanode 驻留多个 TaskTracker。
- 作业被分成多个任务,然后运行到集群中的多个数据节点。
- JobTracker的责任是协调活动调度任务来在不同的数据节点上运行。
- 单个任务的执行,然后由 TaskTracker 处理,它位于执行工作的一部分,在每个数据节点上。
- TaskTracker 的责任是发送进度报告到JobTracker。
- 此外,TaskTracker 周期性地发送“心跳”信号信息给 JobTracker 以便通知系统它的当前状态。
- 这样 JobTracker 就可以跟踪每项工作的总体进度。在任务失败的情况下,JobTracker 可以在不同的 TaskTracker 重新调度它。