MapReduce简介
- MapReduce是Apache Hadoop项目的一个核心模块
- 它是Goole提出的分布式并行编程模型<>论文的Java开源实现
- MapReduce是一个运行在hdfs上的分布式运算程序的编程框架,用于大规模数据集(大于1TB)的并行计算
MapReduce的作用
程序由单机版扩成分布式时,会引入大量的复杂工作。为了提高开发效率,可以将分布式程序中的公共功能封装成框架,让开发人员将精力集中于业务逻辑。
引入MapReduce框架后,开发人员可以将绝大部分工作集中在业务逻辑的开发上,而将分布式计算中的复杂性交由框架来处理。
MapReuce的核心思想
- MapReduce设计的一个理念是"计算向数据靠拢"(移动计算),而不是"数据向计算靠拢"(移动数据)。将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,移动到有数据存储的集群节点上,减少网络传输开销,提高计算效率。
- MapReduce一个完整的运算分为Map和Reduce两个部分,Map会处理本节点的原始数据,产生的数据会临时存储到本地磁盘。Reduce会跨节点fetch属于在自己的数据,并进行处理,产生的数据存储到HDFS上。
MapReduce运行流程
一个完整的MapReduce程序在分布式运行时有三类实例进程:
- MRAppMaster:负责整个程序的过程调度及状态协调
- MapTask:负责map阶段的整个数据处理流程
- ReduceTask:负责reduce阶段的整个数据处理流程
大概流程如下:
- MapReduce程序启动时,会先启动进程Application Master,它的主类是MRAppMaster
- application Master启动之后,根据本次job的描述信息,计算出inputsplit的数据(MapTask的数量)
- application Master然后向ResourceManager申请对应数量的container来执行MapTask进程
- MapTask进程启动之后,根据对应的inputSplit来进行数据处理:
- 利用客户指定的inputFormat来获取RecordReader读取数据,形成KV键值对
- 将KV传递给客户定义的Mapper类的map方法,做逻辑运算,并将map方法的输出KV收集到缓存
- 将缓存中的KV数据按照K分区排序后不断地溢出到磁盘文件
- application master监控master进程完成之后,会根据用户指定地参数来启动相应地Reduce Task进程,并告知ReduceTask要处理地数据范围
- ReduceTask启动之后,根据Application Master告知的待处理的未知数据,从若干已经存到磁盘的数据中取数据,并在本地进行一个归并排序。然后,再按照相同的key的kv为一组,调用客户自定义的Reduce方法,并收集输出结果KV,然后根据用户指定的OutputFormat将结果存储到外部设备。
分片机制
分片的概念
MapReduce在进行作业提交时,会预先对将要分析的原始数据进行划分处理,形成一个个等长的逻辑数据对象,称之为"输入分片"。MapReduce为每一个分片构建一个单独的MapTask,并由该任务来运行用户自定义的Map方法,从而处理分片中的每一条记录。
MapTask的运行流程
- MapTask调用FileInputFormat的getRecordReader读取分片数据
- 每行读取一次,返回一个(K,V)对,K是offset,V是一行数据
- 将K-V对交给MapTask处理
- 每对K-V调用一次map(K, V, context)方法,然后context.write(k, v)
- 写出的数据交给收集器OutputCollector.collector()处理
- 将数据写入环形缓冲区,并记录写入的起始偏移量,终止偏移量,环形缓冲区大小100MB
- 默认写到80%的时候要溢写到磁盘,溢写磁盘的过程数据继续写入到剩余20%
- 溢写磁盘之前要先进行分区,然后在分区内进行排序
- 默认的排序规则是Key的字典排序,使用的是快速排序
- 溢写会形成多个文件,在MapTask读取完一个分片数据后,先将环形缓冲区数据冲刷写到磁盘
- 将数据多个溢写文件进行合并,分区内排序(外部排序==>归并排序)
ReduceTask的运行流程 - 数据按照分区规则发送到ReduceTask
- ReduceTask将来自多个MapTask的数据进行合并,排序(外部排序==>归并排序)
- 按照key相同分组()
- 一组数据调用一次reduce(k,iterablevalues, context)
- 处理后的数据交给reducetask
- reducetask调用FileOutputFormat组件
- FileOutputFormat组件中的Write方法将数据写出
Shuffle和排序
MapReduce确保每个reducer的输入都是按键排序的。系统执行排序,将输出作为输入传给reducer的过程称为shuffle。
map端
map函数开始产生输出时,并不是简单地将它写到磁盘。这个过程很复杂,它利用缓冲的方式写到内存,并出于效率的考虑进行预排序。
每个map任务都有一个环形内存缓冲区用于存储任务输出,在默认情况下,缓冲区的大小为100MB,这个值可以通过改变mapreduce.task.io.sort.mb属性来调整。一旦缓冲内容达到阈值(默认为80%),一个后台线程便开始把内容溢出(spill)到磁盘。在溢写到磁盘过程中,map输出继续写到缓冲区,但是如果在此期间缓冲区被填满,map会被阻塞直到写磁盘过程完成。溢出写过程按照轮询方式将缓冲区的内容写到mapreduce.cluster.local.dir属性在作业特定子目录下指定的目录中。
在写磁盘之前,线程首先根据数据最终要传的reducer把数据划分成相应的分区(partition)。在每个分区中,后台线程按键在内存中排序,如果有一个combiner函数,它就会在排序后的输出上运行。运行combiner函数使得map输出结果更加紧凑,因此减少写到磁盘的数据和传递到reducer的数据。
每次内存缓冲区达到溢出阈值,就会新建一个溢出文件(spill file),因此在map任务写完其最后一个输出记录之后,会有几个溢出文件。在任务完成之前,溢出文件被合并成一个已经分区且已经排好序的输出文件。
如果至少存在3个溢写文件(通过mapreduce.map.combine.minspills)时,combiner就会在输出文件写到磁盘之前再次运行。
reduce端
map输出文件位于运行map任务的tasktracker的本地磁盘,tasktracker需要为分区文件运行reduce任务。并且,reduce任务需要集群上若干个map任务的map输出作为其特殊的分区文件。每个map任务的完成时间可能不同,因此在每个任务完成时,reduce任务就开始赋值其输出。这就是reduce任务的复制阶段。reduce任务有少量复制线程,因此能够并行取得map输出。
如果map输出相当小,会被复制到reduce任务JVM的内存,否则map输出将被复制到磁盘。一旦,缓冲区达到阈值大小,则合并后溢出写到磁盘。
复制完所有的map输出后,reduce任务进入到排序阶段(更恰当的说法是合并阶段,因为排序都是在map端进行的),这个阶段合并map输出,维持其顺序排序,这是循环进行的。
在最后的阶段,即Reduce阶段,直接将数据输入到reduce函数,从而省略一次磁盘往返形成。此阶段的输出直接写到输出文件系统。
编程部分见Maven