什么是MapReduce?
MapReduce是一个分布式计算框架
它将大型数据操作作业分解为可以跨服务器集群并行执行的单个任务
适用于大规模数据处理场景
每个MapReduce执行的过程叫一个job,每个job包含Map和Reduce两个任务,每个任务叫一个Task
JobTracker和TaskTracker
Job Tracker
运行在Namenode
接收客户端Job请求
提交给Task Tracker
Task Tracker
运行在datanode上
从Job Tracker接受任务请求
执行map、reduce等操作
返回心跳给Job Tracker
MapReduce执行流程
- 客户端向namenode发出请求执行mapreduce
- namenode根据请求的路径找到对应的文件,并启动mapreduce
- Mapreduce调用InputFormat类(默认为TextInputFormat)的getSplit()方法,对文件进行切片,这里的切片并不是内存中真实的切分,只是逻辑上的分割,那么切片大小怎么定呢?看源码
long splitSize = computeSplitSize(blockSize, minSize, maxSize);
protected long computeSplitSize(long blockSize, long minSize,long maxSize) {
return Math.max(minSize, Math.min(maxSize, blockSize)); }
//mapreduce的默认切片最小值为0,最大值为Long的最大值,所以经过computeSplitSize的计算,取值一般为block块的大小即128M,一个逻辑块对应一个mapper任务,若要减少mapper的个数,就可以在mapreduce-site.xml配置文件中修改默认逻辑块的最大值和最小值来控制最终逻辑块的大小
**//切片大小确定了,那么文件具体怎么切割呢**
//length为文件的总长度,byteRemaining为剩余的字节长度,此循环对文件进行切块
//首先判断剩余字节数量对切块的大小的比值是否大于SPLIT_SLOP(默认为1.1),如果大于此值则一直切下去,若小于1.1,则就停止循环,此时若比值小于1,则就是最后一块的字节长度大于12.8,此时取得的逻辑块就是文件的最后一个数据块,若比值大于1小于1.1,此时逻辑块就是最后两块数据块的和,最后将splits返回
List<InputSplit> splits = new ArrayList<InputSplit>();
long bytesRemaining = length;
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, splitSize,blkLocations[blkIndex].getHosts(),blkLocations[blkIndex].getCachedHosts()));
bytesRemaining -= splitSize;
}
- 切块切完了,mapreduce调用InputFormat类的CreateRcordReader()方法按照配置文件返回的逻辑块的字符串分隔符,返回一个LineRecordReader对象(包含k-v属性的对象),并调用LineRecordReader中的getCurrentKey()和getCurrentValue()方法读取每行的键值对(k-v)传输到map中去处理
- Mapper类通过map方法实现业务逻辑,生成map,此时生成的是lmap(k,v)并通过Contxt对象输出到OutputCollector收集器
- OutputCollector把收集的(k-v)键值对写入环形缓冲区
环形缓冲区本质上是一个数组,目的是为了更有效地利用空间,在内存中尽可能多的存放数据,防止阻塞
环形缓冲区默认大小是100M,只写80%(环形缓冲区其实是一个数组,前面写着,后面有组件清理缓冲区数据,写到文件防止溢出),当缓冲区的容量超过80%就会触发spill溢出(小文件)到本地磁盘
如果在task输出结果过多时,可能会撑爆内存,此时需要在一定条件下将缓冲区内的数据写入临时磁盘,这个过程就叫溢写,溢写由单独的线程完成,不影响向map缓冲区内写入map的线程,溢出阈值为0.8,在溢出过程,task的输出结果可以往剩下的20%内存中写,互不影响
- 溢出前通过k的hash值对reducer数量取模的值将map进行分区处理,并通过快排和外部排序对分区内的map根据key进行排序,此时生成的有序的map(k,vs)数据,组内有序
- 文件溢出时,写入磁盘之前若自定义了combiner类,则会溢出数据进行,进行进一步处理
- 以上步骤完成之后,在map节点生成了很多的小文件。这些小文件是经过类Partition类做了分区处理的,并且内阁区内的键值对是有序的,但是由于后面需要将文件传入reduce的机器节点,此时的小文件太多,数据迁移和写入磁盘速度较慢,需要在每个map节点上对数据再次进行处理,处理的过程就是将键的hash值对reducer数量取模相同的键值对分在一个分区,此时分区的数量就是reducer的个数,分完区后再对组内的数据进行排序,此时map节点上的任务已经完成,在map节点机器的磁盘中生成了(k,values)键值对
10.reduce节点,根据自己的分区号,到每个map节点上区拉取对应分区的数据,写入自己节点上的磁盘内,边拷贝边将拉取的小文件合并成大文件,由于数据来自不同的map节点,在reduce处理之前,reducer节点会对数据再次进行一次处理,调用GroupingComparator对文件进行分区(此时分区是按照相同k值的键值对放在一组内), 并且根据每组键值对的k进行排序,此时整个shuffle过程就结束了,生成有序的(K-values)键值对 - 进入reduce处理,每次取一组k-vs,进行reduce处理
- 将处理生成的键值对k-v通过context对象传入OutputFormat类中
- 通过OutOutFormat类的RecorderWrite()方法将键值对输出到指定的文件part-r-xxx中