MapReduce是一种可用于数据处理的编程模型。Hadoop可以运行各种语言版本的MapReduce程序。MapReduce程序本质上是并行运行的,因此可以将大规模的数据分析任务分发给任何一个拥有足够多机器的数据中心。MapReduce的优势在于处理大规模的数据集。
--<<Hadoop权威指南>>
名词解释:
- 什么叫做编程模型:
可以简单理解为框架,即别人已将将程序的运行流程,内部处理写好,你只要在其规划好的接口里进行你自己的代码操作,其他事情交给模型去处理,使用对应的编程模型能大大提高编程的速度。
- 什么叫做并行:
多个任务同时进行。
- 什么叫做并发:
多个任务交替进行,看起来像是并行,其实每一个任务执行时,都只有自己在执行,只是任务交替时间太短,看起来像一起执行。
- 环形缓冲区:
这里的指的是shuffle中的环形缓冲区,其实可以看成一个空间循环利用的数组,固定一个数组大小,然后从下标开始往后写,同时spiller不断把写到数组里的内容写入文件中,这样,当数组下标写到快要接近尾部的时候,前面位置的数据已经被写到文件中了,呢么就可以把新的数据重新写到数组下标为0的位置然后继续写,这样就形成了一个不断循环的环型缓存空间。之所以说是快到尾端的时候。是因为必须留下一部分空间,以方便对数组中的数据进行排序等操作。
首先是MapReduce的几个比较大的特点:
1.并行运行:
MapReduce程序本质上是并行运行的,因此可以将大规模的数据分析任务分发给任何一个拥有足够多机器的数据中心。
2.可用多种编程语言实现:
MapReduce可以用Java,Ruby,Python,Shell等实现,并在Hadoop中运行。
3.把处理向数据迁移:
传统高性能计算系统通常有很多处理器节点与一些外存储器节点相连,如用存储区域网络(Storage Area,SAN Network)连接的磁盘阵列,因此,大规模数据处理时外存文件数据I/O访问会成为一个制约系统性能的瓶颈。为了减少大规模数据并行计算系统中的数据 通信开销,代之以把数据传送到处理节点(数据向处理器或代码迁移),应当考虑将处理向数据靠拢和迁移。MapReduce采用了数据/代码互定位的技术方法,计算节点将首先尽量负责计算其本地存储的数据,以发挥数据本地化特点,仅当节点无法处理本地数据时,再采用就近原则寻找其他可用计算节点,并把数据传送到该可用计算节点。
4.顺序处理数据、避免随机访问数据:
大规模数据处理的特点决定了大量的数据记录难以全部存放在内存,而通常只能放在外存中进行处理。由于磁盘的顺序访问要远比随机访问快得多,因此 MapReduce主要设计为面向顺序式大规模数据的磁盘访问处理。为了实现面向大数据集批处理的高吞吐量的并行处理,MapReduce可以利用集群中 的大量数据存储节点同时访问数据,以此利用分布集群中大量节点上的磁盘集合提供高带宽的数据访问和传输。
MapReduce的组成:
MapReduce 任务过程分为两个处理阶段:map 阶段和 reduce 阶段。每个阶段都以键值对作为输入和输出,程序员需要实现 map 和 reduce 这两个函数,另外,Context 中包含了很多便于编程的东西。
map :
Mapper 类是一个泛型,有四个参数,分别指定了 map 函数的输入键、输入值、输出键和输出值。
为了能够支持序列化和反序列化,key和 value 都必须实现 Writable 接口,同时,为了支持排序,key还需要实
现 WritableComparable 接口。
map()方法对每一个<Key,Value>调用一次
Job.setMapperClass(Class)
reduce :
Reducer 类也是一个泛型,有四个参数,分别指定了 reduce 函数的输入键、输入值、输出键和输出值。
序列化与反序列化要求与 Mapper 相同。
reduce()方法对每一组相同Key的<Key,Value>调用一次
Job.setReducerClass(Class)
//设置reduce任务的数量
Job.setNumReducerTasks(int number)
MapReduce 任务执行节点选择:
Hadoop会把作业分成若干个任务(task)来执行,其中包括两类任务,map任务和reduce任务。Hadoop吧MapReduce的输入数据划分成等长的小数据块,称为输入分片(input split),或简单称为分片。Hadoop为每个分片构建一个map任务,并由该任务来运行用户自定义的map函数从而处理分片中的每条数据。
Hadoop会尽量让map任务在其要处理的数据块的存储节点上运行,因为这样可以获得最佳性能,无需使用珍贵的集群带宽资源,这就是所谓的“数据本地化优化”。
对于大多数作业来说,一个合理的分片大小趋向于一个HDFS块的大小,默认128M。因为它是确保可以存储在单个节点的最大数据块的大小,如果分片跨越两个数据块,而在集群中,一般一个HDFS节点是不会存储同一数据的两个数据块的,那么就很有可能需要把在其他节点上的数据块通过网络传输到map处理节点,这样一来,就会降低执行效率。
reduce任务一般就不具有数据本地化优势,单个reduce任务的输入通常来自于所有的Map任务的输出。排过序的Map输出通过网络传输到Reduce任务运行的节点,在reduce端合并,然后由用户定义的reduce函数处理,reduce任务的输出一般储存在HDFS中,以实现可靠存储。
Map的输出到Reduce时,中间发生的事情(Shuffle):
Map的输出先是会到一个环形缓冲区中,然后再不断的spillter写出成一个个文件,并且在写出成文件前,还会对数据进行分区和排序,使数据做到分区且区内有序(Partitioner负责分区,Map的Key的compareTo()方法实现排序逻辑)。
当数据全部读取结束时,所有数据都已经被写成了一个个文件,这时候就会对写出的文件进行一个合并操作(merge),并对同一个分区内的数据再次进行排序(因为是对一个个有序数据进行总排序,所以使用归并排序),最后形成一个map任务的最终结果文件(一个分区且区内有序的最终文件)。
当然,当map有多个任务时,每一个map都会生成一个属于自己数据的最终结果文件。
reduce主动去下载属于自己那份分区的文件,也就是下载所有map各自最终文件中的属于自己的那份分区的数据,把它们下载到reduce所在节点的硬盘上,并对这些数据再次进行归并排序,最后得到一份属于该reduce的有序数据。
reduce为这份数据中key相同的数据分为一组,为每一组调用一次reduce方法,(输出同一组的第一个的key,以及一个可以迭代出同一组的所有value的迭代器)至于key的相同与否,是通过GroupingComparaor(key,nextKey)判断的,所以如果我们想要自定义key的类型或者key的比较方法,就需要重写该方法,如果我们没有重写该方法,则是通过调用key的equals方法进行比较。
MapReduce 输入输出:
InputSplit:
InputSplit 是 Hadoop 中用来把输入数据传送给每一个单独 map。InputSplit 本身不包含数据,而是一个分片长度
和一个记录数据位置的数组。
开发人员不需要直接处理 InputSplit,它是由 InputFormat 负责创建。
总结:一个 split 对应一个 map
InputFormat:
InputFormat 是 Hadoop 的输入格式抽象类,它包含 getSplits 和 createRecordReader 两个方法,分别用来获取分
片和创建记录。
TextInputFormat 是 Hadoop 默认的输入格式。在 TextInputFormat 中,每一个文件(或其中一部分)都会单独作
为 map 的输入,每行数据都会生成一条记录。每条记录则表示成<key,value>形式,其中:key值是每一个数据
的记录在数据分片中的字节偏移量,数据类型是 LongWritable;value 值是每行的内容,数据类型是 Text。
继承顺序:InputFormat->FileInputFormat->TextInputFormat
FileInputFormat 要点:
[1] 作业的输入可以是一个或多个路径。路径可以指向一个文件、目录等。
[2] 如果是目录,默认配置不支持子目录。如果要支持子目录,需设置
mapreduce.input.fileinputformat.input.dir.recursive 为 true。
OutputFormat:
继承顺序:OutputFormat->FileOutputFormat->TextOutputFormat
Hadoop 默认的输出为 TextOutputFormat,每条记录写为一行文本。
Hadoop 还支持其他输入输出格式,如 DBInputFormat 和 DBOutputFormat 用于关系型数据库读写数据,而
TabeInputFormat 和 TableOutputFormat 则用于 HBase 的读写
Combiner:
除了map,reduce,其实还有一个可以当成组件看待的东西,Combiner,它可以在map任务执行后将结果发给reduce前先对map的输出进行一次处理,处理逻辑和reduce类似,不同的是reduce处理的数据可能会来着所有map的结果,而Combiner只处理自己这个map任务的输出结果,可以利用这个特性先对一些数量较大的key的组进行合并或者其他操作,然后再给reduce去下载。 Job.setCombinerClass(Class),但是Combiner会被调用几次需要开发人员自己通过执行逻辑确定,所以必须谨慎使用,以防影响最后的正确结果。
大量小文件时的优化策略:
因为FileInputFormat的切片策略是,无论文件多小,只要是一个文件就默认是一个切片,这样一来,一旦有大量小文件,就会启动大量的map任务进行处理,效率十分的低下,针对这种情况,我们需要做一些优化措施
- 在数据采集时,先进行处理,即现把收集到的小文件合并成一个个大文件再存入HDFS中,然后再用MapReduce进行处理。(如果可以,最好采用该方法)
- 不要使用FileInputFormat进行文件的读取切片,而是使用另一种InputFormat来读取并切片,例如CombineTextFileInputFormat,它的可以把多个小文件在逻辑上归于一个切片中(可以通过代码设置,决定一个切片的最小大小和最大大小),这样就可以用一个map任务处理多个小文件了。
MapReduce小识记:
- map任务的输出写入到本地硬盘中,而不是HDFS中,因为map的输出只是中间结果,并不是最终数据,还要经过reduce的处理后,才是最终数据,一旦作业完成,map任务的结果就可以删除,吧这样的结果存入HDFS还进行备份,实在太过浪费资源。
- Map任务的切片,切片计划是在客户端实现的,在客户端去获取文件大小,并做好逻辑上的切片,然后把切片规划和代码一起发到集群上去,由map执行
- reduce任务的个数是由实现者自己定义的,而map任务的个数是根据配置文件对切片的大小设置以及文件大小自动分配的。
- map的结果去到哪个reduce,叫做map结果的分区,可以自己写一个类继承Partitioner,并实现其getPartition方法,然后在代码中指定使用这个partitioner,(若代码中没有指定,默认使用HashPartitioner)
- reduce的数量要和map结果的分区数量搭配使用才有意义,如果分区数量大于reduce数量,有时候就会报错(除非reduce数量为1,这时所有分区都会发往这一个唯一的reduce),如果分区数量小于reduce数量,有些reduce就不会接受到任何map的输出,导致资源浪费。