Hadoop~MapReduce
一、 MR概述
MapReduce是一个分布式运算的编程框架。
将用户编写的逻辑代码和框架自带的组件组合成一套完整的分布式运算程序。
二、 MR的特点
1. 优点
- MR可以通过简单的编程实现。
- MR扩展性高,可以通过简单的增加机器提升框架性能。
- 容错性高,如果某一个节点上的任务挂了,系统会将此任务转移到另一台节点上继续运行,并且赋予了
4次
重试机制。 - MR适合PB级以上的数据量的离线运算。
2. 缺点
- MR不擅长计算实时需求,不能做到毫秒级的任务处理。
- 不擅长流式计算。
- 不擅长
DAG
计算,即将一次运算的结果当作下一次运算的输入数据。
三、 Hadoop的序列化
1. 为什么需要序列化
将内存中的对象存放到磁盘IO中长期存储、或者放到网络中传输,需要进行序列化处理。反之,将数据信息加载到内存中就需要进行反序列化。
2. 为什么不适用Java的序列化机制
Java拥有一套完善的序列化机制,但是Java的序列化信息十分繁重,除了需要的数据信息之外,还包括各种校验信息、子父类继承关系等,Hadoop的性能受到影响。
3. Hadoop如何实现序列化
Hadoop中封装了自己的序列化机制,实现Hadoop的序列化,只需要继承Hadoop的序列化抽象类,并重写==write()、readFields()==方法。
4. Hadoop序列化的特点
- 结构紧凑,节省了数据空间。
- 传输速度快。
- 支持多语言平台的交互。
四、MR的工作机制
1. MR机制的核心思想
-
Mapper阶段将数据输入并初始化,数据按行读取到MapTask中,并将数据按照K-V对分组,最后输出到Reducer。
-
Reducer阶段将输入的数据,按照相同的Key,将Values进行汇总,按照业务分区将结果输出。
Reducer阶段的数据输入依赖Mapper阶段的数据输出。但是Mapper阶段和Reducer阶段是并行运行的,互不影响。MapTask和ReduceTask也是并行运行。
MR的简易流程框架
Input数据输入 — InputFormat初始化数据 — Mapper — Shuffle
— Reducer – OutputFormat — Output
由此可见,一套MR流程中只能存在一个Map阶段和Reduce阶段,如果业务逻辑复杂,只能编写多个MR程序,并串联MR执行。
2. MR执行过程
MR的执行过程大致可以分为两个阶段:
- 提交Job,将输入数据进行初始化。
- 执行Job,运行MR程序。
1. MR的详细流程框架
Input数据输入 — InputFormat初始化数据 — map sort— copy sort group reduce
— Reducer – OutputFormat — Output
由此可见,shuffle阶段就是将map阶段的数据copy,并且对数据进行排序、分组,在输出到reduce阶段,由Reducer执行运算。
无论是map阶段还是shuffle阶段,都需要将数据进行排序,map阶段的排序使用的是快速排序,shuffle阶段使用的是归并排序。
2. 数据初始化
数据初始化就是将数据切片
并按行读取到内存中。这一工作流程由InputFormat执行。由此可知,数据切片发生在Job的提交阶段。
1. 数据切片
InputFormat声明了getSplit()方法,但是具体的流程由他的实现类FileInputFormat
实现。流程如下(源码流程分析):
- 首先检查目标数据的路径是否合法。
- 判断目标数据中的递归文件夹是否也需要切片,默认是略过不切(false)。
- 判断目标数据是否为空。
- 获取目标数据的路径和真实数据。
- 判断目标数据是否支持切片,如果不支持切片,就把该数据交给一个MT处理。
- 从元数据中获取目标数据的数据块大小信息,并计算切片的大小。
- 切片算法:(剩余数据量 / 切片大小 > 1.1)?,如果结果大于1.1则继续切片,否则不切。
2. 小文件数据切片
因为Hadoop对小文件的处理效率低,所以专门针对小文件制定了切片方式 ---- CombineTextInputFormat。流程如下:
- 先对小文件整合,进行虚拟的逻辑分块,设置虚拟切块的阈值。
- 如果一个文件小于阈值,则会单独分成一块,如果 阈值 < 文件 < 2倍阈值,则会将 文件平均分成两份,并再次与阈值比较,并按上述规律切分。
- 如果文件 > 2倍阈值,则将文件先按阈值分一块,在进行上述比较。
- 将所有虚拟块排列,并逐一组合,虚拟块的和>阈值,则分为一片。
3. shuffle工作流程
shuffle中存在一块环形缓冲区,从数组的中间位置,分别向两边存储信息,数组左边存储存储kv的元数据,右边存储kv值。当缓冲区读入达到80%时,数组会反向读入数据,并将已经读入的数据溢写,溢写时按照k的值进行排序、分区。直到所有数据全部完成落盘,再将数据输入到reduce阶段,按照分区合并,最后输出数据。
4. 分区机制
设置分区数量需要在Job提交阶段执行,使用==SetNumReduceTesks()==方法设定分区数。在数据进入shuffle之前,已将分区编号设置完毕,所以Map阶段会按照数据分区把数据重新分组。
1. 默认分区规则
数据提交之后,Maptask中会通过write方法获取到数据的kv值,并计算分区编号。通过调用环形缓冲区对象实现分区编号的计算。如果设置的分区编号大于1,通过反射的形式创建分区方式的类的对象。如果没有指定分区方式,则默认按照计算k的哈希值的方式计算分区编号(k的哈希值跟integer最大值取余的结果,在跟分区数量取模,是一种另类的随即方式)。如果没有设置分区数量,则默认返回一个分区。
2. 自定义分区
实现自定义分区,需要编写自定义分区的实现类。
- 自定义分区类,并继承分区抽象类,重写分区方法。
- 在方法中,按照业务需求逻辑,对目标数据进行分区,并指定分区编号,最后将编号返回。
- 再提交job前,使用SetPartitionerClass()方法,设置分区规则。
分区器注意事项:
1.设置的RT数量 > 实际应用到的数量, 会生成空白的分区结果文件
2…设置的RT数量 < 实际应用到的数量, 必定报错
3.设置的RT数量=1, 会将所有结果放进一个分区。