简单介绍
官方给出的介绍是hadoop MR是一个用于轻松编写以一种可靠的、容错的方式在商业化硬件上的大型集群上并行处理大量数据的应用程序的软件框架。
MR任务通常会先把输入的数据集切分成独立的块(可以看成是一个较小数据集),然后这些块由map任务以完全并行的方式的去处理。map任务输出的结果排完序之后会交给reduce去处理得到最终结果。MR负责调度,监听并重新执行失败的任务,它的输入和输入都存储在hdfs上。
MR框架由一个主节点 ResourceManager, 一个或多个从节点 NodeManager以及每个应用程序独有的 MRAppMaster 组成。通常而言, MR 框架和hdfs运行在一组相同的的节点,即执行计算任务和负责数据存储的节点是同一个,这样的配置允许MR能够在存在数据的节点上高效地调度任务。
对于MR程序的开发,开发者只需要指定输入输出路径,通过实现适当的接口、抽象类以及一些其他的任务参数便可以完成。
任务流程
这里以MR的inputs-outputs、map、reducet三个方面来深入了解MR任务流程。
Inputs-outputs
MR框架仅操作<key, value> 形式的键值对,即MR把输入的数据视为一组<key, value>,并输出一组<key, value>。值得一提的是输入和输出的<key, value>在数据类型上并没有任何联系。
MR处理数据的核心思想是移动计算而不是移动数据,有些类似于就近原则,即哪个节点有数据,计算任务就在哪个节点执行,这和把数据全移动到同一节点计算相比,效率要高很多。所以一个MR job的执行首先要做的就是把需要执行的计算程序copy一份,然后传输到有数据的节点,因此MR操作的对象<key, value>必须能被MR序列化,也就需要实现 Writable 接口,同时由于MR会对输出的<key, value> 排序,所以也必须实现 WritableComparable 接口。
inputs-outputs在MR中位置如下:
(input) <k1, v1> -> map -> <k2, v2> -> combine -> <k2, v2> -> reduce -> <k3, v3> (output)。
input
MR的输入依靠InputFormat实现,InputFormat为MR任务实现了以下三点:
- 验证任务的输入规格。
- 将输入的文件切分成逻辑切片(InputSplit),并将之分配给mapper任务
- 提供了RecordReader从InputSplit上读取数据供mapper处理
默认的输入格式( InputFormat)是TextInputFormat。
output
MR的输入依靠OutputFormat实现,OutputFormat为MR任务实现了以下两点:
- 验证任务的输出规格,如文件路径是否已存在
- 提供RecordWriter将输出写入文件系统。
默认的输出格式是TextOutputFormat。
Mapper
对于InputFormat产生的每一个InputSplit,MR框架都会启动一个map任务,该任务会把RecordReader 传来的<key, value> 映射成一组中间值(即一组新的<key, value> )。值得注意的是中间值的数据类型和输入的数据类型是不需要一致的,而且一个给定的<key, value> 对会可能被映射成0个或者多个中间值。
mapper的实现是通过 Job.setMapperClass(Class)方法传递给job的,然后MR调用map(WritableComparable, Writable, Context) 方法处理输入的<key, value>对,输出的结果通过调用context.write(WritableComparable, Writable)方法汇总。
在map阶段,启动的map的数量通常是由输入文件的大小所决定,即是输入文件的block的总数。也可以通过Configuration.set(MRJobConfig.NUM_MAPS, int)手动设置。
map输出的中间值会调用OutpCollector.collect(key,value),在该方法内部,先调用Partitioner.getPartition()(通过job.setPartitionerClass(class)控制,每一个分区对应一个reducer)获取该记录的分区号,然后将<key,value,partition>传给MapOutputBuffer.collect(), MapOutputBuffer内部使用了一个内部的环形的缓冲区来暂时保存用户的输出数据,当缓冲区使用率达到一定阀值后,由SpillThread线程将缓冲区中的数据spill到本地磁盘上(在此阶段会先对partition分区号排序,然后再按照key排序(通过Job.setSortComparatorClass(Class)控制,若没有设置,则默认调用key内部实现的compareTo方法),然后会把数据写入文件到临时目录(若有设置combiner,写入文件之前会对分区数据做聚集操作,生成key和value的集合组成的键值对)并把元数据写入SpillRecord)。【详细见参考3】
我们还可以通过Job.setCombinerClass(Class)指定一个combiner来对map数据在本地进行聚合操作,这样做可以减少从mapper传输到reducer的数据量。
Reducer
reducer的主要负责将同一个key所对应的value值合并。而reduce任务的个数可以由 Job.setNumReduceTasks(int)设置。
reducer的实现是通过Job.setReducerClass(Class) 传递给job的,然后MR框架会调用 reduce(WritableComparable, Iterable<Writable>, Context)方法方法处理分组输入的每个key及其所对应的value的集合所组成的<key, value>对。
Reducer 主要分为三个阶段: shuffle, sort 以及 reduce.
shuffle
在这个阶段,MR框架会通过HTTP从各个mapper所在节点获取与本地reduce任务相关的分区的数据
sort
在此阶段,MR会对从各个节点获取的数据通过key进行分组(调Job.setSortComparatorClass(Class)指定的Comparator,此处的数据对象是从各个mapper汇集过来的输出数据,在map阶段处理的是当前的一个map的数据)
Secondary Sort
此阶段(若有)会调用 Job.setGroupingComparatorClass(Class)指定的分组函数类对数据分组(决定了哪些key可以分为一组无论是否相同)生成key对应的value迭代器,这个迭代器的key使用属于同一个组的所有key的第一个key。
reduce
本阶段会调用reduce(WritableComparable, Iterable<Writable>, Context) 处理传过来的每一个<key, (list of values)>并通过Context.write(WritableComparable, Writable)写入 FileSystem (文件系统)。
与map不同,reduce的输出结果是无序的。
参考:
https://insight.io/github.com/apache/hadoop/blob/trunk/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/Job.java
http://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html
http://flyingdutchman.iteye.com/blog/1878775