一.MapReduce
的思想核心是
分
而治之
, 充分利用了并行处理的优势。
- Mapper map()方法是对输入的一个KV对调用一次!!
- Reduce Reduce()方法是对相同K的一组KV对调用执行一次
- Drive
二.
MapReduce
原理分析
- MapTask运行机制详解:
1.
首先,读取数据组件
InputFormat
(默认
TextInputFormat
)会通过
getSplits
方法对输入目录中文件进行逻辑切片规划得到splits
,有多少个
split
就对应启动多少个
MapTask
。
split
与
block
的对应关系默认是一对一。
2.
将输入文件切分为
splits
之后,由
RecordReader
对象(默认
LineRecordReader
)进行读取,以
\n 作为分隔符,读取一行数据,返回<key
,
value>
。
Key
表示每行首字符偏移值,
value
表示这一行 文本内容。
3.
读取
split
返回
<key,value>
,进入用户自己继承的
Mapper
类中,执行用户重写的
map
函数,RecordReader读取一行这里调用一次。
4. map
逻辑完之后,将
map
的每条结果通过
context.write
进行
collect
数据收集。在
collect
中,会先对其进行分区处理,默认使用HashPartitioner
。
MapReduce
提供
Partitioner
接口,它的作用就是根据
key
或
value
及
reduce
的数量来决定当前的这对
输出数据最终应该交由哪个
reduce task
处理。默认对
key hash
后再以
reduce task
数量取模。默认的
取模方式只是为了平均
reduce
的处理能力,如果用户自己对
Partitioner
有需求,可以订制并设置到
job
上。
5.
接下来,会将数据写入内存,内存中这片区域叫做环形缓冲区,缓冲区的作用是批量收集
map
结果,减少磁盘IO
的影响。我们的
key/value
对以及
Partition
的结果都会被写入缓冲区。当然写入之前,key
与
value
值都会被序列化成字节数组。
环形缓冲区其实是一个数组,数组中存放着
key
、
value
的序列化数据和
key
、
value
的元数据信息,包括partition
、
key
的起始位置、
value
的起始位置以及
value
的长度。环形结构是一个抽象概念。
缓冲区是有大小限制,默认是100MB
。当
map task
的输出结果很多时,就可能会撑爆内存,所以需要在一定条件下将缓冲区中的数据临时写入磁盘,然后重新利用这块缓冲区。这个从内存往磁盘写数据的过程被称为Spill
,中文可译为溢写。这个溢写是由单独线程来完成,不影响往缓冲区写map结果的线程。溢写线程启动时不应该阻止
map
的结果输出,所以整个缓冲区有个溢写的比例spill.percent。这个比例默认是
0.8
,也就是当缓冲区的数据已经达到阈值(
buffer size * spillpercent = 100MB * 0.8 = 80MB),溢写线程启动,锁定这
80MB
的内存,执行溢写过程。
Maptask的输出结果还可以往剩下的
20MB
内存中写,互不影响。
6
、当溢写线程启动后,需要对这
80MB
空间内的
key
做排序
(Sort)
。排序是
MapReduce
模型默认的行为
!
如果
job
设置过
Combiner
,那么现在就是使用
Combiner
的时候了。将有相同
key
的
key/value
对的value加起来,减少溢写到磁盘的数据量。
Combiner
会优化
MapReduce
的中间结果,所以它在整个模型中会多次使用。
那哪些场景才能使用
Combiner
呢?从这里分析,
Combiner
的输出是
Reducer
的输入,
Combiner绝不能改变最终的计算结果。Combiner
只应该用于那种
Reduce
的输入
key/value
与输出
key/value类型完全一致,且不影响最终结果的场景。比如累加,最大值等。Combiner
的使用一定得慎重,如果用好,它对job
执行效率有帮助,反之会影响
reduce
的最终结果。
7.
合并溢写文件:每次溢写会在磁盘上生成一个临时文件(写之前判断是否有
combiner
),如果map的输出结果真的很大,有多次这样的溢写发生,磁盘上相应的就会有多个临时文件存在。当整个数据处理结束之后开始对磁盘中的临时文件进行merge
合并,因为最终的文件只有一个,写入磁盘,并且为这个文件提供了一个索引文件,以记录每个reduce
对应数据的偏移量。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
maptask的并行度(数量)
注意:默认spitsize == blocksize ==128m 这样的好处是为了所谓的数据本地化或hdfs的短路读取,减少一些没必要的网络资源。
MapTask
并行度是不是越多越好呢?(源码中的split_slop=1.1)
答案不是,如果一个文件仅仅比
128M
大一点点也被当成一个
split
来对待,而不是多个
split.
MR
框架在并行运算的同时也会消耗更多资源,并行度越高资源消耗也越高,假设
129M
文件分为两个分片,一个是128M
,一个是
1M
;对于1M
的切片的
Maptask
来说,太浪费资源。
2.ReduceTask 工作机制:
Reduce
大致分为
copy
、
sort
、
reduce
三个阶段,重点在前两个阶段。
copy
阶段包含一个eventFetcher来获取已完成的
map
列表,由
Fetcher
线程去
copy
数据,在此过程中会启动两个
merge
线程,分别为inMemoryMerger
和
onDiskMerger
,分别将内存中数据
merge
到磁盘和将磁盘中的数据 进行merge
。待数据
copy
完成之后,
copy
阶段就完成了,开始进行
sort
阶段,
sort
阶段主要是执行 finalMerge操作,纯粹的
sort
阶段,完成之后就是
reduce
阶段,调用用户定义的
reduce
函数进行处理。
详细步骤
- Copy阶段,简单地拉取数据。Reduce进程启动一些数据copy线程(Fetcher),通过HTTP方式请求 maptask获取属于自己的文件。
- Merge阶段。这里的merge如map端的merge动作,只是数组中存放的是不同map端copy来的数 值。Copy过来的数据会先放入内存缓冲区中,这里的缓冲区大小要比map端的更为灵活。merge 有三种形式:内存到内存;内存到磁盘;磁盘到磁盘。默认情况下第一种形式不启用。当内存中的 数据量到达一定阈值,就启动内存到磁盘的merge。与map 端类似,这也是溢写的过程,这个过 程中如果你设置有Combiner,也是会启用的,然后在磁盘中生成了众多的溢写文件。第二种 merge方式一直在运行,直到没有map端的数据时才结束,然后启动第三种磁盘到磁盘的merge 方式生成最终的文件。
- 合并排序。把分散的数据合并成一个大的数据后,还会再对合并后的数据排序。对排序后的键值对调用reduce方法,键相等的键值对调用一次reduce方法,每次调用会产生零个或者多个键值对,最后把这些输出的键值对写入到HDFS文件中。
ReduceTask并行度
MapTask
的并发数由切片数决定,ReduceTask
数量的决定是可以直接手动设置:
注意事项
1. ReduceTask=0
,表示没有
Reduce
阶段,输出文件数和
MapTask
数量保持一致;
2. ReduceTask
数量不设置默认就是一个,输出文件数量为
1
个;
3.
如果数据分布不均匀,可能在
Reduce
阶段产生倾斜;
三.shuffle机制
![](https://i-blog.csdnimg.cn/blog_migrate/62253f9411a23f483cc77525eb161d95.png)
map输入到reduce这个阶段的过程称为shuffle;
job对象进入后会有分区:
自定义分区小结:
分区是在map方法输出后在缓冲区内做的,他的参数就是map的输出的参数。
1.
自定义分区器时最好保证分区数量与
reduceTask
数量保持一致;
2.
如果分区数量不止
1
个,但是
reduceTask
数量
1
个,此时只会输出一个文件。
3.
如果
reduceTask
数量大于分区数量,但是输出多个空文件
4.
如果
reduceTask
数量小于分区数量,有可能会报错
combiner合并小结
![](https://i-blog.csdnimg.cn/blog_migrate/58ca6a6aa4f5fd5f10af145179cbbb3a.png)
1. Combiner
是
MR
程序中
Mapper
和
Reducer
之外的一种组件
2. Combiner
组件的父类就是
Reducer
3. Combiner
和
reducer
的区别在于运行的位置
4. Combiner
是在每一个
maptask
所在的节点运行
;
5. Combiner
的意义就是对每一个
maptask
的输出进行局部汇总,以减小
网络传输量
。
6. Combiner
能够应用的前提是不能影响最终的业务逻辑,此外,
Combiner
的输出
kv
应该跟
reducer 的输入kv
类型要对应起来。
排序
MapTask
它会将处理的结果暂时放到环形缓冲区中,当环形缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,溢写完毕后,它会对磁盘上所有文件进行归并排序。
ReduceTask
当所有数据拷贝完毕后,
ReduceTask
统
-
对内存和磁盘上的所有数据进行一次归并排序。
1.
部分排序
.
MapReduce
根据输入记录的键对数据集排序。保证输出的每个
文件内部有序
。
2.
全排序
最终输出结果只有
一个文件
,且文件内部有序。实现方式是只设置
- -
个
ReduceTask
。但该方法在处理大型文件时效率极低,因为- -
台机器处理所有文件,完全丧失了
MapReduce
所提供的并行架构。
3.
辅助排序
: ( GroupingComparator
分组
)
在
Reduce
端对
key
进行分组。应用于
:
在接收的
key
为
bean
对象时,想让一个或几个字段相同
(
全部字段比较不相同)
的
key
进入到同一个
reduce
方法时,可以采用分组排序。
4.
二次排序
.
在自定义排序过程中,如果
compareTo
中的判断条件为两个即为二次排序。
GroupingComparator 是reduce端的组件:
GroupingComparator
是mapreduce当中reduce端的一个功能组件,主要的作用是决定哪些数据作为一组,调用一次reduce的逻辑,默认是每个不同的key,作为多个不同的组,每个组调用一次reduce逻辑,我们可以自定义GroupingComparator实现不同的key作为同一个组,调用一次reduce逻辑。
MapReduce
读取和输出数据:
InputFormat:InputFormat
是
MapReduce
框架用来读取数据的类。
分类:
- TextInputFormat (普通文本文件,MR框架默认的读取实现类型)
- KeyValueTextInputFormat(读取一行文本数据按照指定分隔符,把数据封装为kv类型)
- NLineInputF ormat(读取数据按照行数进行划分分片)
- CombineTextInputFormat(合并小文件,避免启动过多MapTask任务)
- 自定义InputFormat
CombineTextInputFormat:MR框架默认的TextInputFormat切片机制按文件划分切片,文件无论多小,都是单独一个切片, 然后由一个MapTask处理,如果有大量小文件,就对应的会生成并启动大量的 MapTask,而每个 MapTask处理的数据量很小大量时间浪费在初始化资源启动收回等阶段,这种方式导致资源利用 率不高。
CombineTextInputForma
t
用于小文件过多的场景,它可以将
多个小文件从逻辑上划分成一个切
片
,这样多个小文件就可以交给一个
MapTask
处理,提高资源利用率。
使用代码:
//
如果不设置
InputFormat
,它默认用的是
TextInputFormat.class
job
.
setInputFormatClass
(
CombineTextInputFormat
.
class
);
//
虚拟存储切片最大值设置
4m
CombineTextInputFormat
.
setMaxInputSplitSize
(
job
,
4194304
);
CombineTextInputFormat的原理:切片生成过程分为两部分:虚拟存储过程和切片过程
假设设置
setMaxInputSplitSize
值为
4M
四个小文件:
1.txt -->2M ;2.txt-->7M;3.txt-->0.3M;4.txt--->8.2M
虚拟存储过程:把输入目录下所有文件大小,依次和设置的
setMaxInputSplitSize
值进行比较,如果不大于设置的最大值,逻辑上划分一个块
。如果输入文件大于设置的最大值且大于两倍,那么以最大值切割一块;当剩余数据大小超过设置的最大值且不大于最大值2
倍,此将文件均分成2
个虚拟存储块(防止出现太小切片)。 比如如setMaxInputSplitSize
值为
4M
,输入文件大小为
8.02M
,则先逻辑上分出一个
4M
的块。剩余的大小为4.02M
,如果按照
4M
逻辑划分,就会出现
0.02M
的非常小的虚拟存储文件,所以将剩余的4.02M
文件切分成(
2.01M
和
2.01M
)两个文件。
1.txt-->2M;2M<4M;
一个块;
2.txt-->7M;7M>4M,
但是不大于两倍,均匀分成两块;两块:每块
3.5M
;
3.txt-->0.3M;0.3<4M ,0.3M<4M ,
一个块
4.txt-->8.2M;
大于最大值且大于两倍;一个
4M
的块,剩余
4.2M
分成两块,每块
2.1M
所有块信息:
2M
,
3.5M
,
3.5M
,
0.3M
,
4M
,
2.1M
,
2.1M
共
7
个虚拟存储块。
切片过程
判断虚拟存储的文件大小是否大于
setMaxInputSplitSize
值,大于等于则单独形成一个
切片。
如果不大于则跟下一个虚拟存储文件进行合并,共同形成一个切片。
按照之前输入文件:有
4
个小文件大小分别为
2M
、
7M
、
0.3M
以及
8.2M
这四个小文件,
则虚拟存储之后形成
7
个文件块,大小分别为:
2M
,
3.5M
,
3.5M
,
0.3M
,
4M
,
2.1M
,
2.1M
最终会形成
3
个切片,大小分别为:
(
2+3.5
)
M
,(
3.5+0.3+4
)
M
,(
2.1+2.1
)
M
注意:虚拟存储切片最大值设置最好根据实际的小文件大小情况来设置具体的值。
自定义inputformat:
方案:
将多个小文件合并成一个
SequenceFile
文件(
SequenceFile
文件是
Hadoop
用来存储二进制形式的
key-value
对的文件格式),
SequenceFile
里面存储着多个文件,存储的形式为文件路径
+
名称为key,文件内容为
value
。
outputformat:
OutputFormat:
是
MapReduce
输出数据的基类,所有
MapReduce
的数据输出都实现了
OutputFormat抽象类。下面我们介绍几种常见的OutputFormat
子类
TextOutputFormat
默认的输出格式是
TextOutputFormat
,它把每条记录写为文本行。它的键和值可以是任意类型,因为TextOutputFormat
调用
toString()
方 法把它们转换为字符串。
SequenceFileOutputFormat
将
SequenceFileOutputFormat
输出作为后续
MapReduce
任务的输入,这是一种好的输出格式,因为它的格式紧凑,很容易被压缩。
四 数据压缩机制:
需要压缩的地方:
Map输入端压缩 Map输出端压缩 Reduce端输出压缩
压缩配置:
设置
map
阶段压缩
Configuration configuration = new Configuration();
configuration.set("mapreduce.map.output.compress","true");
configuration.set("mapreduce.map.output.compress.codec","org.apache.hadoop.io.compress.SnappyCodec");
设置
reduce
阶段的压缩
configuration.set("mapreduce.output.fileoutputformat.compress","true");
configuration.set("mapreduce.output.fileoutputformat.compress.type","RECORD" );
configuration.set("mapreduce.output.fileoutputformat.compress.codec","org.ap ache.hadoop.io.compress.SnappyCodec");
配置文件压缩:
<property>
<name>mapreduce.output.fileoutputformat.compress</name>
<value>true</value>
</property>
<property>
<name>mapreduce.output.fileoutputformat.compress.type</name>
<value>RECORD</value>
</property>
<property>
<name>mapreduce.output.fileoutputformat.compress.codec</name>
<value>org.apache.hadoop.io.compress.SnappyCodec</value>
</property>