目录
1. Mapreduce 入门
1.1 什么是 MapReduce
hadoop 的四大组件:
- HDFS:分布式存储系统
- MapReduce:分布式计算系统
- YARN:hadoop 的资源调度系统 Common:以上三大组件的底层支撑组件,主要提供基础工具包和 RPC 框架等
MapReduce 是一个分布式运算程序的编程框架,是用户开发“基于 Hadoop 的数据分析应用” 的核心框架
MapReduce 核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布 式运算程序,并发运行在一个 Hadoop 集群上
1.2 为什么需要 MapReduce
- 海量数据在单机上处理因为硬件资源限制,无法胜任
- 而一旦将单机版程序扩展到集群来分布式运行,将极大增加程序的复杂度和开发难度
- 引入 MapReduce 框架后,开发人员可以将绝大部分工作集中在业务逻辑的开发上,而将 分布式计算中的复杂性交由框架来处理
设想一个海量数据场景下的数据计算需求:
- 单机版:磁盘受限,内存受限,计算能力受限
- 分布式版:
1、 数据存储的问题,hadoop 提供了 hdfs 解决了数据存储这个问题
2、 运算逻辑至少要分为两个阶段,先并发计算(map),然后汇总(reduce)结果 3、 这两个阶段的计算如何启动?如何协调?
4、 运算程序到底怎么执行?数据找程序还是程序找数据?
5、 如何分配两个阶段的多个运算任务?
6、 如何管理任务的执行过程中间状态,如何容错?
7、 如何监控?
8、 出错如何处理?抛异常?重试?
可见在程序由单机版扩成分布式版时,会引入大量的复杂工作。为了提高开发效率,可以将 分布式程序中的公共功能封装成框架,让开发人员可以将精力集中于业务逻辑。
Hadoop 当中的 MapReduce 就是这样的一个分布式程序运算框架,它把大量分布式程序都会 涉及的到的内容都封装进了,让用户只用专注自己的业务逻辑代码的开发。它对应以上问题 的整体结构如下:
- MRAppMaster:MapReduce Application Master,分配任务,协调任务的运行
- MapTask: 并发任务,负责 mapper 阶段的任务处理 进程YARNChild( jps进程)
- ReduceTask: 汇总任务,负责 reducer 阶段的任务处理 进程YARNChild( jps进程)
1.3 mapreduce编写规范
- 用户编写的程序分成三个部分:Mapper,Reducer,Driver(提交运行 MR 程序的客户端)
- Mapper 的输入数据是 KV 对的形式(KV 的类型可自定义)
- Mapper 的输出数据是 KV 对的形式(KV 的类型可自定义)
- Mapper 中的业务逻辑写在 map()方法中
- map()方法(maptask 进程)对每一个<K,V>调用一次
- Reducer 的输入数据类型对应 Mapper 的输出数据类型,也是 KV 对的形式
- Reducer 的业务逻辑写在 reduce()方法中
- Reducetask 进程对每一组相同 k 的<K,V>组调用一次 reduce()方法
- 用户自定义的 Mapper 和 Reducer 都要继承各自的父类
- 整个程序需要一个 Drvier 来进行提交,提交的是一个描述了各种必要信息的 job 对象
2. MapReduce 程序的核心运行机制
2.1 概述
一个完整的 MapReduce 程序在分布式运行时有两类实例进程:
- MRAppMaster:负责整个程序的过程调度及状态协调
- Yarnchild:负责 map 阶段的整个数据处理流程
- Yarnchild:负责 reduce 阶段的整个数据处理流程
以上两个阶段 MapTask 和 ReduceTask 的进程都是 YarnChild,并不是说这 MapTask 和 ReduceTask 就跑在同一个 YarnChild 进行里
2.2 MapReduce 程序的运行流程
-
一个 mr 程序启动的时候,最先启动的是 MRAppMaster,MRAppMaster 启动后根据本次 job 的描述信息,计算出需要的 maptask 实例数量,然后向集群申请机器启动相应数量的 maptask 进程
-
maptask 进程启动之后,根据给定的数据切片(哪个文件的哪个偏移量范围)范围进行数 据处理,主体流程为:
A、利用客户指定的 InputFormat 来获取 RecordReader 读取数据,形成输入 KV 对
B、将输入 KV 对传递给客户定义的 map()方法,做逻辑运算,并将 map()方法输出的 KV 对收 集到缓存
C、将缓存中的 KV 对按照 K 分区排序后不断溢写到磁盘文件 -
MRAppMaster 监控到所有 maptask 进程任务完成之后(真实情况是,某些 maptask 进 程处理完成后,就会开始启动 reducetask 去已完成的 maptask 处 fetch 数据),会根据客户指 定的参数启动相应数量的 reducetask 进程,并告知 reducetask 进程要处理的数据范围(数据 分区)
-
Reducetask 进程启动之后,根据 MRAppMaster 告知的待处理数据所在位置,从若干台 maptask 运行所在机器上获取到若干个 maptask 输出结果文件,并在本地进行重新归并排序, 然后按照相同 key 的 KV 为一个组,调用客户定义的 reduce()方法进行逻辑运算,并收集运 算输出的结果 KV,然后调用客户指定的 OutputFormat 将结果数据输出到外部存储
2.3 MapTask 并行度决定机制
maptask 的并行度决定 map 阶段的任务处理并发度,进而影响到整个 job 的处理速度
那么,mapTask 并行实例是否越多越好呢?其并行度又是如何决定呢?
一个 job 的 map 阶段并行度由客户端在提交 job 时决定,客户端对 map 阶段并行度的规划 的基本逻辑为:
将待处理数据执行逻辑切片(即按照一个特定切片大小,将待处理数据划分成逻辑上的多 个 split),然后每一个 split 分配一个 mapTask 并行实例处理
这段逻辑及形成的切片规划描述文件,是由 FileInputFormat 实现类的 getSplits()方法完成的。 该方法返回的是 List,InputSplit 封装了每一个逻辑切片的信息,包括长度和位置 信息,而 getSplits()方法返回一组 InputSplit
2.4 切片机制
FileInputFormat 中默认的切片机制:
- 简单地按照文件的内容长度进行切片
- 切片大小,默认等于 block 大小
- 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片 比如待处理数据有两个文件:
File1.txt 200M
File2.txt 100M
经过 getSplits()方法处理之后,形成的切片信息是:
File1.txt-split1 0-128M
File1.txt-split2 129M-200M
File2.txt-split1 0-100M
FileInputFormat 中切片的大小的参数配置:
通过分析源码,在 FileInputFormat 中,计算切片大小的逻辑:
long splitSize = computeSplitSize(blockSize, minSize, maxSize),翻译一下就是求这三个值的中间值
blocksize:默认是 128M,可通过 dfs.blocksize 修改
minSize:默认是 1,可通过 mapreduce.input.fileinputformat.split.minsize 修改
maxsize:默认是 Long.MaxValue,可通过 mapreduce.input.fileinputformat.split.maxsize 修改
因此,
如果 maxsize 调的比 blocksize 小,则切片会小于 blocksize
如果 minsize 调的比 blocksize 大,则切片会大于 blocksize
但是,不论怎么调参数,都不能让多个小文件“划入”一个 split
2.5 maptask 并行度及切片机制源码分析
maptask:执行map阶段的任务称为maptask
reducetask:执行reduce阶段的任务称为reducetask
并行度:maptask一起执行的个数 换句话说在一个job中运行maptask的个数
2.5.1 maptask运行的并行度分析
底层的数据如何存储的:分块多副本存储的。
假如有300M文件,
- blk1: 0-127M
- blk2: 128-255M
- blk3: 256-300M
maptask运行的个数,是和数据的大小有关的,假设blocksize=128M,那么一个maptask对应的数据量是多少呢?
- 如果是130M——>数据的跨节点传输的问题 (这130M会在两个block中)
- 如果是100M——>数据存在跨节点传输的问题
- 那么这样想来1个maptask——>一个数据块的大小(128M)比较合理
事实上一个maptask对应的数据是一个逻辑切片的大小
- 一个逻辑切片----128M 一个块的大小
- 逻辑切片:逻辑上的概念 一个数据范围的划分 并没有真正的切分数据
- 物理切片:是逻辑切片的对立面
- 一个逻辑切片对应一个maptask任务
2.5.2 切片源码分析
首先我们想文件切片发生在什么时候?既然一个逻辑切片对应一个maptask任务,那必然要先对文件进行切片,才能知道要运行多少个maptask,所以在文件加载的时候,我们就要计算逻辑切片数,我们在写代码的时候Driver中都会设置文件的输入FileInputFormat,所以入口在这里,去加载源码hadoop-2.7.7-src/hadoop-project。
FileInputFormat文件加载器,这里边儿有一个getSplits方法 获取所有的切片。
org.apache.hadoop.mapreduce.lib.input.FileInputFormat
/**
* Generate the list of files and make them into FileSplits.
* @param job the job context
* @throws IOException
*
* 切片获取的方法
* InputSplit 代表的是文件切片的对象
* 返回值:List<InputSplit> 所有的文件切片的列表
* 参数:JobContext job job的上下文对象
*
*/
public List<InputSplit> getSplits(JobContext job) throws IOException {
StopWatch sw = new StopWatch().start();
//获取最小切片大小
/*
* getFormatMinSplitSize():1
* getMinSplitSize(job):0
* minSize=1
*/
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
/*
* 切片最大大小
* maxSize=long.max
*/
long maxSize = getMaxSplitSize(job);
// generate splits
//最终返回list集合
List<InputSplit> splits = new ArrayList<InputSplit>();
//获取FileInputFormat.addInputPath("")输入路径所有