MapReduce工作机制
1.map阶段深度解析:
* 首先InputFormat根据getSplits()方法(由FileInputFormat实现),对待处理目录下的所有待处理文件默认按照分块block的大小(128M)对待处理文件进行逻辑拆分,最终返回的splits的个数就是YARN开启的mapTask的个数;
* 每个mapTask使用TextInputFormat中的RecordReader(由LineRecordReader实现)开始读取逻辑切片中的文本数据,LineRecordReader默认按照换行符"\n"来对文本分割,因此每读取一行就会返回一对键值对,key是起始行的偏移量,value是读取到的这一行的内容,每读取到一行数据就会调用一次mapper中的map()方法;
* map()对数据处理完毕后,调用context.write()方法;如果每调用一次write方法就对文件进行一次IO操作,会造成资源浪费;因此会使用OutputCollector.collect()方法收集write()输出数据,并且默认使用HashPartitioner对收集的数据分区;
* 接着,将context.write()输出的键值对数据和分区结果转换成字节数组,并写入内存,这块内存叫做环形缓冲区(本质是一个字节数组);环形缓冲区的默认大小是100M,溢出比是0.8,当环形缓冲区中存储的数据超过80M时,便会再开启一个溢出线程来负责把这80M的数据溢写(spill)到一个临时文件中,并对key按照字典排序;而剩下的20M内存依然可以并行保存数据;如果环形缓冲区的数据非常少,环形缓冲区至少会将数据序列化到临时文件一次;
* 在内存缓冲区溢出数据到临时文件时,会先判断是否设置了Combiner,如果设置了,那就执行数据局部汇总;此时得到了分区且排序的临时文件;最终对这同一个mapTask产生的临时文件列表merge合并,得到一个新的文件,并为这个文件提供一个索引文件(记录reduce阶段的key的偏移量),等待reduceTask使用;注意:每个mapTask(逻辑切片)对应一个最终文件和索引文件
总结:
分区: OutputCollector.collect()阶段
排序: 环形缓冲区溢出到临时文件阶段
2.reduce阶段深度解析:
* copy阶段:每个reduceTask开启一个fetcher线程,通过Http get请求的方式,从mapTask文件列表中读取属于自己partition的数据到内存缓冲区;通过job.setNumReduceTasks()设置reduceTask个数(同时间接改变分区pertition个数);
* merge阶段:merge阶段分为三种模式,内存--内存(默认不启用),内存--磁盘,磁盘--磁盘;在fetch线程读取数据的过程中,每当读取的数据达到内存容量的阀值,便会将内存数据溢出到磁盘上,产生一个新的临时文件(内存--磁盘),如果存在Combiner,Combiner也会生效;最终fetch读取数据完毕后,会对所有的临时文件再进行一次merge,并且排序,产生一个新的文件;每个reduceTask对应一个最终merge文件;
* reduce阶段:reduceTask每次读取reduceTask文件数据,key分组key,value迭代器(封装了这一组key对应的value),对数据进行处理后,调用context.write()方法;底层使用OutputFormat使用"\t"对key和value进行分割,并在末尾输出"\n"换行符,输出到part-r-00000文件中(最后一个数字代表分区数);
总结:
排序:最终磁盘--磁盘阶段排序
3.shuffle阶段:
shuffle是MapReduce的核心阶段;shuffle阶段是指从mapTask阶段产生输出数据开始,到reduceTask阶段获取到完整的数据作为输入之前的一阶段;Shuffle中的缓冲区大小会影响到mapreduce程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快 ;缓冲区的大小可以通过参数调整,参数:io.sort.mb 默认100M,shuffle主要分布如下:
mapTask: collect阶段(收集并分区)-->spill阶段(排序)-->merge阶段
reduceTask: copy阶段-->内存磁盘merge-->磁盘磁盘merge(排序)
MapReduce并行机制
1.mapTask并行机制
在客户端将任务提交给YARN前,会使用InputFormat(由FileInputFormat实现)中的getSplits()方法,
计算逻辑切片的个数;mr程序会逐个遍历待处理文件列表,默认按照splitSize = Math.max(minSize,
Math.min(maxSize, blockSize)),即逻辑切片大小等于HDFS分块大小128M来切分;切分完成会形成切片规划文件job.split,最终每个切片对应启动一个mapTask;
注意:
* 无论minSize(1),blockSize(128M),maxSize(Long.MAX_VALUE)如何设置,都不能将待处理文件列表的小
文件划分到同一个逻辑切片中;因此当需要对大量小文件处理时,应该先手动合并小文件,再提交给mr程序来处理;
* 在计算出splitSize大小后,InputFormat还会再次计算bytesRemaining(剩余文件大小)/splitSize >
1.1,如果为false,那么最后剩余的文件就会划分到本次的逻辑切片中;即:129M的文件实际上只会划分为一个逻辑
切片
2.reduceTask并行机制
redecuTask并行度是通过job.setNumReduceTasks()手动设定的,需要注意的是设置reduceTask并行度时,应该由于数据分区不均匀,导致的数据倾斜问题
3.task优化
* 无论是mapTask还是reduceTask,都应该保证它的运行时间在1分钟以上
* 默认情况下,每次启用mapTask和reduceTask,都会创建一个JVM实例,可以设置JVM重
用,mapred.job.reuse.jvm.num.tasks
* 如果待处理的文件非常大,可以适当增加HDFS分块大小(512M);
MapReduce其他功能
1.计数器Counter
使用MapReduce Counter来观察job运行期间的参数细节
Counter counter = context.getCounter("selfCounter","myCounter");
counter.increment(1);
2.多job串联
一个job的输出文件需要作为另外一个job的输入文件,可以使用MapReduce内置的JobControl完成,但是效率偏低,通常使用工作流任务调度工具来实现,azkaban
3.MapReduce实战案例:
案例一:倒排索引
需求:统计待处理文件单词出现的次数及位置
eg: hello a.txt:1 b.txt:2
解决步骤:
* map阶段,
FileSplit fileSplit = (FileSplit)context.getInputSplit();
Strin filename = fileSplit.getPath.getName();
* combiner阶段,
局部聚合单词+文件名组合成的key,并统计次数;最终输出<hello,a.txt:1>
* reduce阶段,
全局聚合单词对应的key,在各文件出现的次数
案例二:数据去重
需求:去除多个文件下的每一行相同的数据
* map阶段,
读取这一行数据,并直接输出,context.write(new Text(line),NullWritable.get());
* reduce阶段,
不需要迭代,直接输出,context.write(key,NullWritable.get());
案例三:TOP-N
需求:获取多个文件中的前5个最大值
* map阶段,
map()方法不直接输出输出,而是将数据保存到TreeMap中,并且
if(treeMap.size()>5){
treeMap.remove(treeMap.firstKey());
}
重新cleanup()方法,cleanup()方法在map阶段结束前会调用一次
context.write(NullWritable.get(),new IntWriteble(i));
* reduce阶段,将迭代器数据装入集合,排序,并遍历,取前5个
YARN
1.YARN介绍:
Yet Another Resource Negotiator,另一种资源协调者,YARN是一个通用资源管理系统和调度平台,用户向YARN申请资源,YARN就为用户分配资源
2.YARN的组件:
ResourceManager:整个集群环境下的资源管理者,统计NodeManager的信息,为ApplicationMaster分配资源(内存和CPU);
NodeManager:集群下的每个节点都对应一个NodeManager,NodeManager定时向ResourceManager汇报心跳和资源使用情况,如果ResourceManager宕机,它会自动连接上备用ResourceManager;同时NodeManager处理来自ApplicationMaster的容器container的开启关闭请求;
ApplicationMaster:用户提交的请求都包含一个ApplicaitonMaster,它负责向ResourceManager申请资源(container),并跟NodeManager通信以开启关闭容器,ApplicationMaster是整个用户进程的处理者,由ApplicationMaster来维护容器内的资源使用
3.Yarn运行流程:
客户端的YarnRunner向ResourceManager提交应用程序,ResourceManager从NodeManager中分配一个container给客户端;客户端请求NodeManager开启容器,并开始运行ApplicationMaster,
ApplicationMaster向ResourceManager注册自己,并保持连接心跳;ApplicationMaster根据客户端提交的切片规划文件信息,需要多少个mapTask就向ResourceManager再申请多少个容器;然后再与NodeManager协商开启容器,当mapTask执行完毕,ResourceManager关闭容器,并继续执行reduceTask,再次申请容器;最终ApplicationMaster也执行完毕,向ResourceManager汇报销毁信息,并注销自己
4.资源调度器Scheduler
*FIFO Scheduler:先进先出,按客户端提交的顺序调度资源;缺点:如果一个需要占用大量时间的任务先执行,后续的小任务都得等待
*Capacity Scheduler(默认):根据配置文件中的资源分配占比,不同的队列能够使用不同的资源;缺点:如果一直使用某一个队列,导致其他没被使用的队列的内存一直空闲
*Fair Scheduler:Fair调度器能动态的为job分配系统资源;缺点:分配资源时,需要先销毁一部分资源,为新来的Job准备资源
调度器的使用是通过yarn-site.xml配置文件中的;如果我们没有定义任何队列,所有的应用将会放在一个default队列中。对于Capacity调度器,队列名必须是队列树中的最后一部分