分布式计算框架——MapReduce
一、MapReduce基本概述
MapReduce是一种分布式计算模型,是Google提出的,主要用于搜索领域,解决海量数据的计算问题。
MR有两个阶段组成:Map和Reduce,用户只需实现map()和reduce()两个函数,即可实现分布式计算。
二、MapReduce执行流程
上面图片来源:MapReduce过程详解及其性能优化
上面图片来源:MapReduce中各个阶段的分析
2.1 Input阶段
Input阶段主要是负责整个MapReduce的输入,用于读取你要处理的数据。
MapReduce中了解的三个读取类
-
TextInputFormat:MapReduce默认使用的类
- 用于读取文件的,默认读取fs.defaultFS属性对应的文件系统中的文件
-
DBInputFormat:用于读MySQL等数据
- Sqoop底层原理就是这个类
-
TableInputFormat:用于读取Hbase的数据
输入: hadoop spark hbase hive hive spark hbase hbase hive hive spark hbase hive spark hadoop spark hbase hive hive spark hbase hbase hive hive spark hbase hive spark
2.1.1 MapReduce默认分片规则
问题1:map的数量由什么决定?
1、文件的个数
2、文件的大小
3、配置参数
mapreduce.input.fileinputformat.split.minsize //启动map最小的split size大小,默认0
mapreduce.input.fileinputformat.split.maxsize //启动map最大的split size大小,默认256M
dfs.block.size//block块大小,默认64M
计算公式:splitSize = Math.max(minSize, Math.min(maxSize, blockSize));
举例说明:
例1:输入文件一个,大小300M,则分为3个Split:
Split1:128M
Split2:128M
Split3:44M
例2:输入文件三个,大小分别为50M,50M,200M则分为4个Split:
Split1:50M
Split2:50M
Split3:128M
Split4:72M
例3:输入文件一个,大小为135M,则分为1个Split。
Split1:135M
划分分片的计算公式:
-
如果不大于,这个文件单独作为一个分片
-
如果大于,减去一个splitSize的大小,作为一个分片,剩下的再次判断
分片: Split1: hadoop spark hbase hive hive spark hbase hbase hive hive spark hbase hive spark hadoop spark hbase hive Split2: hive spark hbase hbase hive hive spark hbase hive spark
Input输出为键值对K1V1,K1为偏移量,V1为每一行的数据。(注:下列数据中的100,200,300不是实际值,只是示例)
Split1:
0 hadoop spark hbase hive
100 hive spark hbase hbase hive
200 hive spark hbase hive spark
300 hadoop spark hbase hive
Split2:
0 hive spark hbase hbase hive
100 hive spark hbase hive spark
2.2 Map阶段
在map阶段,每一个MapTask进程都会实例化一个Mapper类对象,对K1V1调用map方法。如单词统计里面的,对每一行的进行统计,出现则次数为1。
MapTask1:
hadoop 1
spark 1
hbase 1
hive 1
hive 1
spark 1
hbase 1
hbase 1
hive 1
hive 1
spark 1
hbase 1
hive 1
spark 1
hadoop 1
spark 1
hbase 1
hive 1
MapTask2:
hive 1
spark 1
hbase 1
hbase 1
hive 1
hive 1
spark 1
hbase 1
hive 1
spark 1
map阶段输出K2V2,其中K2为单词,V2为单词次数
2.3 shuffle阶段
2.3.1 Map端的shuffle
每一个MapTask都会执行一遍MapShuffle过程。这里以MapTask1为例进行说明。
-
step1:先分区
每一个MapTask会对自己输出的数据调用分区类分区,标记每一条数据会被哪个Reduce处理。reduce个数参数设置为job.setNumReduceTasks(2);MapTask1: K2 V2 part hadoop 1 0 spark 1 1 hbase 1 1 hive 1 0 hive 1 0 spark 1 1 hbase 1 1 hbase 1 1 hive 1 0 hive 1 0 spark 1 1 hbase 1 1 hive 1 0 spark 1 1 hadoop 1 0 spark 1 1 hbase 1 1 hive 1 0
默认的分区方法为hash分区,可以通过setPartitionerClass(PhoneNumPartition.class)进行自定义分区。PhoneNumPartition为自定义分区类。下图将“135”开头的电话分在分区1,“136”开头的电话分在分区2,其他都号码分在一个区。
-
step2:Spill阶段【每个MapTask会将当前内存中的数据写入磁盘,变成文件】
每个MapTask输出的K2V2会不断的写入一个环形缓冲区【缓冲区大小默认:100M】,当缓冲区存储达到阈值80%,
就会出现溢写:将这80%的数据写入磁盘,变成一个个小文件。预留20%的空间让MapTask继续写,不影响MapTask的输出。
在溢写之前会对这80%的数据进行排序。排序规则如下:调用K2的compareTo方法,相同分区的数据放在一起并且有序。排序的算法是基于内存的排序算法。最后会将这80%的数据写入磁盘变成文件。
举例:1、第一次写入:达到80%,排序生成文件file1。 hadoop 1 0 hive 1 0 hive 1 0 hive 1 0 hbase 1 1 hbase 1 1 hbase 1 1 spark 1 1 spark 1 1 2、第二次写入:达到80%,排序生成文件file2。 hadoop 1 0 hive 1 0 hive 1 0 hbase 1 1 spark 1 1 spark 1 1 3、最后一次写入,MapTask后面没有数据了,直接做一次Flush。 hive 1 0 hbase 1 1 spark 1 1
在合成大文件之前,要进行partition、sort和combine等操作。通过分区,将不同类型的数据分开处理,之后对不同分区的数据进行排序,如果有Combiner,还要对排序后的数据进行combine(两次Combiner:两次排序之后)。
-
step3: Merge:合并【每个MapTask会将自己对应的所有小文件合并为一个大文件】
合并排序:所有小文件最终会成为一个大文件
排序规则:调用K2对应的compareTo方法。
排序算法:基于磁盘的归并排序【基于有序文件的磁盘排序算法】
合并之后生成一个大文件,实现了MapTask全局有序。(注意:在写磁盘的时候采用压缩的方式将map的输出结果进行压缩是一个减少网络开销很有效的方法!)MapTask1: hadoop 1 0 hadoop 1 0 hive 1 0 hive 1 0 hive 1 0 hive 1 0 hive 1 0 hive 1 0 hbase 1 1 hbase 1 1 hbase 1 1 hbase 1 1 hbase 1 1 spark 1 1 spark 1 1 spark 1 1 spark 1 1 spark 1 1 MapTask2: hive 1 0 hive 1 0 hive 1 0 hive 1 0 hbase 1 1 hbase 1 1 hbase 1 1 spark 1 1 spark 1 1 spark 1 1
-
step4:整个Map端的shuffle结束了,每个MapTask结束,会通知程序管理者,程序管理者会通知ReduceTask到每个MapTask的机器上拉取数据。
2.3.2 Reduce端的shuffle
-
step1 拉取数据,每个ReduceTask会到每个MapTask的节点上拉取属于自己的数据。
ReduceTask0: 从MapTask1拉去数据 hadoop 1 0 hadoop 1 0 hive 1 0 hive 1 0 hive 1 0 hive 1 0 hive 1 0 hive 1 0 从MapTask2拉去数据 hive 1 0 hive 1 0 hive 1 0 hive 1 0 ReduceTask1: 从MapTask1拉去数据: hbase 1 1 hbase 1 1 hbase 1 1 hbase 1 1 hbase 1 1 spark 1 1 spark 1 1 spark 1 1 spark 1 1 spark 1 1 从MapTask2拉去数据: hbase 1 1 hbase 1 1 hbase 1 1 spark 1 1 spark 1 1 spark 1 1
-
step2 Merge:合并,将属于自己的每个MapTask的数据进行合并。
合并:将K2相同的数据进行合并排序
排序规则:调用K2的compareTo方法
排序算法:归并算法,实现每个reduce全局有序
每个reduceTask处理的数据如下:ReduceTask0: hadoop 1 0 hadoop 1 0 hive 1 0 hive 1 0 hive 1 0 hive 1 0 hive 1 0 hive 1 0 hive 1 0 hive 1 0 hive 1 0 hive 1 0 ReduceTask1: hbase 1 1 hbase 1 1 hbase 1 1 hbase 1 1 hbase 1 1 hbase 1 1 hbase 1 1 hbase 1 1 spark 1 1 spark 1 1 spark 1 1 spark 1 1 spark 1 1 spark 1 1 spark 1 1 spark 1 1
-
step3 分组。
ReduceTask0: hadoop <1,1> hive <1,1,1,1,1,1,1,1,1,1> ReduceTask1: hbase <1,1,1,1,1,1,1,1> spark <1,1,1,1,1,1,1,1>
默认的分组规则是调用K2自带的compareTo方法来实现分组比较。也可以自定义分组比较器,分组比较器的优先级高于K2自带的比较方法。 job.setGroupingComparatorClass(OrderGroup.class);
2.4 Reduce阶段
当Reducer的输入文件确定后,整个Shuffle操作才最终结束。之后就是Reducer的执行了,reduce阶段,每个reduceTask会对每组数据调用一次reduce(K2, iterator< V2 >)方法。最后Reduce会把结果存到HDFS上。
2.5 Output阶段
通过outputformat方法将结果写到文件中。这里需要注意的是输出的文件夹不能已经存在。否则会抛FileAlreadyExistsException异常。
三、MapReduce的优化
MapReduce优化方法主要从六个方面考虑:数据输入、Map阶段、Reduce阶段、IO传输、数据倾斜问题和常用的调优参数。
3.1 数据输入
(1)合并小文件:由于Hadoop擅长存储大文件,因为大文件的元数据信息比较少,如果Hadoop集群当中有大量的小文件,那么每个小文件都需要维护一份元数据信息,会大大的增加集群管理元数据的内存压力,所以在实际工作当中,如果有必要一定要将小文件合并成大文件进行一起处理。
(2)采用CombineTextInputFormat来作为输入,解决输入端大量小文件的应用场景。
3.2 Map阶段
(1)减少溢写(Spill)次数:通过调整
mapreduce.task.io.sort.mb: I/O 排序内存缓冲 (MiB)
mapreduce.map.sort.spill.percent:I/O 排序溢出百分比
增大触发Spill的内存上限,减少Spill次数,从而减少磁盘IO。
(2)减少合并(Merge)次数:通过调整mapreduce.task.io.sort.factor:I/O 排序因子,增大Merge的文件数目,减少Merge的次数,从而缩短MR处理时间。
(3)在Map之后,不影响业务逻辑的前提下,先进行Combine处理,减少IO。
3.3 Reduce阶段
(1)合理设置Map和Reduce数:合理!!!不是越多越好,也不能太少,太少的话,会导致Task等待,延长处理时间;太多的话,会导致Map、Reduce任务间竞争资源,造成处理超时等错误。
(2)设置Map、Reduce共存:调整mapreduce.job.reduce.slowstart.completedmaps :Reduce 任务前要完成的 Map 任务数量 参数,使Map运行到一定程度后,Reduce也开始运行,减少Reduce的等待时间。
(3)规避使用Reduce: 能够不使用reduce阶段尽量不使用reduce阶段,因为会经过shuffle过程。
(4)合理设置Reduce端的Buffer:当reduce将所有的map上对应自己partition的数据下载完成后,就会开始真正的reduce计算阶段。reducetask真正进入reduce函数的计算阶段,由于reduce计算时肯定也是需要消耗内存的,而在读取reduce需要的数据时,同样是需要内存作为buffer,这个参数是控制,reducer需要多少的内存百分比来作为reduce读已经sort好的数据的buffer大小??默认用多大内存呢??默认情况下为0,也就是说,默认情况下,reduce是全部从磁盘开始读处理数据。可以用mapreduce.reduce.input.buffer.percent(default 0.0)来设置reduce的缓存。如果这个参数大于0,那么就会有一定量的数据被缓存在内存并输送给reduce,当reduce计算逻辑消耗内存很小时,可以分一部分内存用来缓存数据,可以提升计算的速度。所以默认情况下都是从磁盘读取数据,如果内存足够大的话,务必设置该参数让reduce直接从缓存读数据,
3.4 IO传输
(1)采用数据压缩的方式,减少网络IO的时间,安装Snappy和LZO压缩编码器。
(2)使用SequenceFile二进制文件。
3.4.1 压缩方式选择
3.4.1.1 Gzip压缩
优点:压缩率比较高,而且压缩/解压速度也比较快;hadoop本身支持,在应用中处理gzip格式的文件就和直接处理文本一样;大部分linux系统都自带gzip命令,使用方便。
缺点:不支持split。
应用场景:当每个文件压缩之后在130M以内的(1个块大小内),都可以考虑用gzip压缩格式。例如说一天或者一个小时的日志压缩成一个gzip文件,运行mapreduce程序的时候通过多个gzip文件达到并发。hive程序,streaming程序,和java写的mapreduce程序完全和文本处理一样,压缩之后原来的程序不需要做任何修改。
3.4.1.2 Bzip2压缩
优点:支持split;具有很高的压缩率,比gzip压缩率都高;hadoop本身支持,但不支持native;在linux系统下自带bzip2命令,使用方便。
缺点:压缩/解压速度慢;不支持native。
应用场景:适合对速度要求不高,但需要较高的压缩率的时候,可以作为mapreduce作业的输出格式;或者输出之后的数据比较大,处理之后的数据需要压缩存档减少磁盘空间并且以后数据用得比较少的情况;或者对单个很大的文本文件想压缩减少存储空间,同时又需要支持split,而且兼容之前的应用程序(即应用程序不需要修改)的情况。
3.4.1.3 Lzo压缩
优点:压缩/解压速度也比较快,合理的压缩率;支持split,是hadoop中最流行的压缩格式;可以在linux系统下安装lzo命令,使用方便。
缺点:压缩率比gzip要低一些;hadoop本身不支持,需要安装;在应用中对lzo格式的文件需要做一些特殊处理(为了支持split需要建索引,还需要指定inputformat为lzo格式)。
应用场景:一个很大的文本文件,压缩之后还大于200M以上的可以考虑,而且单个文件越大,lzo优点越越明显。
目前企业中常用的是Lzo和Snappy。
3.4.1.4 Snappy压缩
优点:高速压缩速度和合理的压缩率。
缺点:不支持split;压缩率比gzip要低;hadoop本身不支持,需要安装;
应用场景: 当Mapreduce作业的Map输出的数据比较大的时候,作为Map到Reduce的中间数据的压缩格式;或者作为一个Mapreduce作业的输出和另外一个Mapreduce作业的输入。
3.4.2 压缩参数配置
参数 | 默认值 | 阶段 | 建议 |
---|---|---|---|
io.compression.codecs (在core-site.xml中配置) | org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.compress.BZip2Codec | 输入压缩 | Hadoop使用文件扩展名判断是否支持某种编解码器 |
mapreduce.map.output.compress(在mapred-site.xml中配置) | false | mapper输出 | 这个参数设为true启用压缩 |
mapreduce.map.output.compress.codec(在mapred-site.xml中配置) | org.apache.hadoop.io.compress.DefaultCodec | mapper输出 | 使用LZO或snappy编解码器在此阶段压缩数据 |
mapreduce.output.fileoutputformat.compress(在mapred-site.xml中配置) | false | reducer输出 | 这个参数设为true启用压缩 |
mapreduce.output.fileoutputformat.compress.codec(在mapred-site.xml中配置) | org.apache.hadoop.io.compress. DefaultCodec | reducer输出 | 使用标准工具或者编解码器,如gzip和bzip2 |
mapreduce.output.fileoutputformat.compress.type(在mapred-site.xml中配置) | RECORD | reducer输出 | SequenceFile输出使用的压缩类型:NONE和BLOCK |
上述图表来源:Hadoop数据压缩方式对比
3.5 数据倾斜
3.5.1 数据倾斜的现象
简单来说数据倾斜就是数据的key 的分化严重不均,造成一部分数据很多,一部分数据很少的局面。
3.5.2 产生数据倾斜原因
(1)分区规则的原因
MapReduce中默认的分区规则,Key的Hash取余。分区规则不合适,导致数据倾斜
(2)数据的问题
数据本身就是倾斜的,某一种K2的值过多
3.5.3 减少数据倾斜的方法
(1)抽样和范围分区
可以通过对原始数据进行 抽样得到的结果集来预设分区边界值。
(2)自定义分区
(3)combine
使用Combine可以大量减小数据倾斜。在可能的情况下,Combine的目的就是聚合并精简数据。
(4)采用Map Join,尽量避免Recduce join。
3.6 常用的调优参数
3.6.1 资源相关参数
(1)以下参数是在用户自己的MR应用程序中配置就可以生效(mapred-default.xml)
(2)应该在YARN启动之前就配置在服务器的配置文件中才能生效(yarn-default.xml)
(3)Shuffle性能优化的关键参数,应在YARN启动之前就配置好(mapred-default.xml)