一、MapReduce完整流程
MapTask工作机制
ReduceTask工作机制
MapTask工作机制:
(1)Read阶段:MapTask通过用户编写的RecordReader,从输入InputSplit中解析出一个个key/value。
(2)Map阶段:该节点主要是将解析出的key/value交给用户编写map()函数处理,并产生一系列新的key/value。
(3)Collect收集阶段:在用户编写map()函数中,当数据处理完成后,一般会调用OutputCollector.collect()输出结果。在该函数内部,它会将生成的key/value分区(调用Partitioner),并写入一个环形内存缓冲区中。
(4)Spill阶段:即“溢写”,当环形缓冲区满80%后,MapReduce会将数据写到本地磁盘上,生成一个临时文件。需要注意的是,将数据写入本地磁盘之前,先要对数据进行一次本地排序,并在必要时对数据进行合并、压缩等操作。
溢写阶段详情:
步骤1:利用快速排序算法对缓存区内的数据进行排序,排序方式是,先按照分区编号Partition进行排序,然后按照key进行排序。这样,经过排序后,数据以分区为单位聚集在一起,且同一分区内所有数据按照key有序。
步骤2:按照分区编号由小到大依次将每个分区中的数据写入任务工作目录下的临时文件output/spillN.out(N表示当前溢写次数)中。如果用户设置了Combiner,则写入文件之前,对每个分区中的数据进行一次聚集操作。
步骤3:将分区数据的元信息写到内存索引数据结构SpillRecord中,其中每个分区的元信息包括,在临时文件中的偏移量、压缩前数据大小和压缩后数据大小。如果当前内存索引大小超过1MB,则将内存索引写到文件output/spillN.out.index中。
(5)合并阶段:当所有数据处理完成后,MapTask对所有临时文件进行一次合并,以确保最终只会生成一个数据文件。
当所有数据处理完后,MapTask会将所有临时文件合并成一个大文件,并保存到文件output/file.out中,同时生成相应的索引文件output/file.out.index。
在进行文件合并过程中,MapTask以分区为单位进行合并。对于某个分区,它将采用多轮递归合并的方式。每轮合并io.sort.factor(默认10)个文件,并将产生的文件重新加入待合并列表中,对文件排序后,重复以上过程,直到最终得到一个大文件。
ReduceTask工作机制:
(1)Copy阶段:ReduceTask从各个MapTask上远程拷贝一片数据,并针对某一片数据,如果其大小超过一定阈值,则写到磁盘上,否则直接放到内存中。
(2)Merge阶段:在远程拷贝数据的同时,ReduceTask启动了两个后台线程对内存和磁盘上的文件进行合并,以防止内存使用过多或磁盘上文件过多。
(3)Sort阶段:当所有map task的分区数据全部拷贝完,按照MapReduce语义,用户编写reduce()函数输入数据是按key进行聚集的一组数据。为了将key相同的数据聚在一起,Hadoop采用了基于排序的策略。由于各个MapTask已经实现对自己的处理结果进行了局部排序,因此,ReduceTask只需对所有数据进行一次归并排序即可。
(4)Reduce阶段:reduce()函数将计算结果写到HDFS上。
让每个MapTask最终只生成一个数据文件,可避免同时打开大量文件和同时读取大量小文件产生的随机读取带来的开销。
二、性能优化
思维导图:
1、参数优化
Map阶段
从HDFS读取数据
map数量问题
map数量多:存在大量小文件,每个小文件对应一个map任务,map任务启动和
初始化时间大于逻辑处理时间,造成资源浪费。
map数量少:存在大文件,map执行时间长,影响整个任务执行。
原则
使大数据量利用合适的map数
使单个map任务处理合适的数据量(存在文件不大,但是字段少记录多的情况)
切片公式:splitSize=Math.max(minSize, Math.min(maxSize, blockSize));
方案
合并小文件,减少map数,修改相关参数
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
set mapred.max.split.size; --切片最大值,大于这个值要切分
set mapred.min.split.size; --切片最小值,小于这个值要合并
--每个节点处理的最小split,节点的文件大小小于这个值,同一个节点下合并;在多个节点下跨节点合并
set mapred.min.split.size.per.node;
--每个机架处理的最小split,机架的文件大小小于这个值,同一个机架下合并;在多个节点下跨机架合并
set mapred.min.split.size.per.rack;
一般参数值大小关系:max.split.size >= min.split.size >= min.size.per.node >= min.size.per.rack
参数作用优先升级关系:max.split.size <= min.split.size <= min.size.per.node <= min.size.per.rack
增加map数(存在文件不大,但是字段少记录多)
设置reduce个数:set mapred.reduce.tasks=n;
重新写入新表: create table target_table as select * from source_table distribute by rand();
对新表进行逻辑处理,增大map数量,提高执行效率
设置切片最大值、最小值
set mapred.max.split.size; --切片最大值,大于这个值要切分
set mapred.min.split.size; --切片最小值,小于这个值要合并
相关影响
是否压缩&是否可切分
set hive.exec.compress.output; --查看是否开启压缩
set io.compression.codecs; --查看配置了哪些压缩算法
set mapreduce.output.fileoutputformat.compress.codec; --查看使用的压缩算法
参考资料
《MapReduce过程详解及其性能优化》
《真正让你明白Hive参数调优系列1:控制map个数与性能调优参数》
《hive优化之------控制hive任务中的map数和reduce数》
《hive的性能优化之参数调优》
《大多数开发人员都弄错的Hive与MapReduce小文件合并问题》
Partition
partition值默认是通过计算key的hash值后对Reduce task的数量取模获得
写数据到磁盘
环形缓冲区
set mapreduce.task.io.sort.mb;--环形缓冲区字节数组大小,默认100M
set mapreduce.map.sort.spill.percent; --环形缓冲区空间达到该占比后溢出,默认0.80
溢写
Sort
快速排序对缓冲区数据进行排序,先按照分区编号进行排序,再按照key进行排序。
Combiner
set min.num.spill.for.combine; --默认3,即溢写文件最少3个的时候,combiner函数在merge产生结果文件前运行
merge
set mapreduce.task.io.sort.factor;--采用多轮递归合并的方式,合并时最多同时合并的溢写文件数,默认10
对溢写文件,合并文件进行压缩
set mapreduce.map.output.compress; --开启压缩,默认false
set mapreduce.map.output.compress.codec; --设置压缩方式,默认org.apache.hadoop.io.compress.DefaultCodec
Shuffle阶段
Map端Shuffle
排序
规约
合并
Reduce端Shuffle
Copy
set mapreduce.reduce.shuffle.parallelcopies; --Reduce同时到多少个map端fetcher数据的并行度,默认5
set mapreduce.reduce.shuffle.read.timeout; --Reduce下载线程最大的下载时间段,避免网络原因导致误判,默认180000ms
SortMerge
Reduce
set mapreduce.reduce.input.buffer.percent; --reduce处理数据的内存占比,默认为0.0,即全部从磁盘读处理数据;如果内存足够大,可适当分配,提高计算速度
参考资料
《MapReduce执行流程和Shuffle过程》
Reduce阶段
分组
Reduce
Reduce数量问题
Reduce数量多:ReduceTask启动和初始化消耗时间和资源
Reduce数量少:同时过多的Reduce会生成很多个文件,也可能导致小文件问题
原则
使大数据量利用合适的reduce数
使单个reduce任务处理合适的数据量
规则
默认规则
参数1:hive.exec.reducers.bytes.per.reducer --每个reduce任务处理的数据量
参数2:hive.exec.reducers.max --每个任务最大的reduce数量
计算规则:reduceNum = min(参数2, 总数据量/参数1)
手动设置
set mapred.reduce.tasks = n;
只有一个reduce的i情况
情况1:select UDTF聚合函数却没带group by
方案:使用group by
情况2:select count(distinct)
方案:select count(1) from (select column from table group by colum)
情况3:order by全局排序
方案:使用limit
情况4:笛卡尔积,即join时没有关联条件
方案:使用on条件;注意:on条件之间只能用and不能用or
通用参数
set mapreduce.map.memory.mb; --mapTask容器内存
set mapreduce.reduce.memory.mb; --reduceTask容器内存
set io.file.buffer.size; --缓冲池大小,默认4k,可适当调高
2、HQL优化
数据倾斜
原因:MapReduce计算中,大量的相同的key被分配到同一个ReduceTask中,造成数据热点,负载不均衡。
原因
场景1:Join
大表join小表 or 小表join大表
方案1:mapjoin,将小表全部加载到内存中,在map端进行join后输出,避免reducer处理
set hive.auto.covert.join = true; --开启mapjoin功能,如果join中小表小于阈值则转为mapjoin
set hive.mapjoin.smalltable.filesize=26214400; --大表小表的阈值设置(默认25M以下为小表)
方案2:小表放到子查询,小表的查询结果相当于大表的where条件
select a.* from bigtable a where id in (select id from smalltable group by id);
大表join大表
方案1:替换关联字段中的大量空值/重复值
方法1:空key过滤/去重
select t1.,t2.name from
(select * from table1 where length(id)>=1 or id is not null) t1
join
(select * from table2 where length(id)>=1 or id is not null) t2
on t1.id = t2.id ;
OR
select t1. from
(select * from table1 where length(id)>=1 or id is not null) t1
join
(select id from table2 group by id) t2
on t1.id = t2.id ;
OR
select * from log a join user b on a.user_id is not null and a.user_id = b.user_id
union all
select * from log c where c.user_id is null;
方法2:空key转换
select t1.,t2.name
from table1 t1
join table2 t2
on case when t1.id is null or length(id) = 0 then concat(hive,rand()) else t1.id end = t2.id;
方案2:大表切分成小表后mapjoin
例如:日志表关联用户表,对日志表进行小表处理后关联用户表mapjoin
原逻辑:
select * from log a left join users b on a.user_id = b.user_id;
改进后:
select /+mapjoin(x)/ *
from log a
left join
(select /+mapjoin(c)/ d.
from (select distinct user_id from log) c join users d on c.user_id = d.user_id)x
on a.user_id = x.user_id;
场景2:不同数据类型关联
例如:user表中user_id为int,log表中user_id既有string也有int,关联时默认hash按照int类型的user_id分配,导致所有string类型的user_id分配到同一个reducer中
方案:把int类型转为string的user_id
select * from user a left join log b on b.user_id = cast(a.user_id as string);
场景3:group by
问题:map阶段同一个key数据分发给一个reduce,key数据过大导致数据倾斜
方案:数据负载均衡时,查询计划生成两个MRJob。
set hive.map.aggr = true; --是否在Map端进行聚合,默认为true
set hive.groupby.mapaggr.checkinterval = 100000; --在Map端进行聚合操作的条目数目;默认1000000
set hive.groupby.skewindata = true; --有数据倾斜的时候进行负载均衡,默认False
参考资料
《hive.map.aggr、hive.groupby.skewindata执行过程》
场景4:count(distinct)
子主题 1
参考资料
《深入浅出Hive数据倾斜》
《Hive学习之路 (十九)Hive的数据倾斜》
分区裁剪&列裁剪
分区裁剪:读取数据时,对数据分区过滤
列裁剪:读取数据时,只获取需要的列,减少数据输入
谓词下推PPD(Predicate Pushdown)
set hive.optimize.ppd = true; --设置谓词下推
所谓的谓词下推就是在join之前的mr(spark)任务的map阶段提前对表进行过滤优化,使得最后参与join的表的数据量更小
1、对于Join(Inner Join)、Full outer Join,条件写在on后面,还是where后面,性能上面没有区别;
2、对于Left outer Join ,右侧的表写在on后面、左侧的表写在where后面,性能上有提高;
3、对于Right outer Join,左侧的表写在on后面、右侧的表写在where后面,性能上有提高;
select ename,dept_name from E left outer join D on ( E.dept_id = D.dept_id and E.eid='HZ001' and D.dept_id = 'D001'); --dept_id在map端过滤,eid在reduce端过滤
select ename,dept_name from E left outer join D on ( E.dept_id = D.dept_id and D.dept_id = 'D001') where E.eid='HZ001'; --dept_id,eid都在map端过滤
select ename,dept_name from E left outer join D on ( E.dept_id = D.dept_id and E.eid='HZ001') where D.dept_id = 'D001'; --dept_id,eid都在reduce端过滤
elect ename,dept_name from E left outer join D on ( E.dept_id = D.dept_id ) where E.eid='HZ001' and D.dept_id = 'D001'; --dept_id在reduce端过滤,eid在map端过滤
注意:如果在表达式中含有不确定函数,整个表达式的谓词将不会被pushed,例如:
select a.*
from a join b on a.id = b.id
where a.ds = '2019-10-09' and a.create_time = unix_timestamp();
因为unix_timestamp是不确定函数,在编译的时候无法得知,所以,整个表达式不会被pushed,即ds='2019-10-09'也不会被提前过滤。类似的不确定函数还有rand()等。
参考资料
《Hive中的Predicate Pushdown Rules(谓词下推规则)》
3、架构优化
fetch抓取
不通过MapReduce,直接读取存储文件,输出结果;适用于全局查找、字段查找、limit查找
set hive.fetch.task.conversion=more;
本地模式
set hive.exec.mode.local.auto=true; --开启本地mapreduce模式
set hive.exec.mode.local.auto.inputbytes.max=50000000; --当输入数据量小于这个值,启动本地mr
set hive.exec.mode.local.auto.input.files.max=5; --当输入文件个数小于这个值,启动本地mr
并行执行
把一个sql语句中没有相互依赖的阶段并行去运行。提高集群资源利用率
set hive.exec.parallel=true; --开启并行执行,默认false
set hive.exec.parallel.thread.number=16; --同一个sql允许最大并行度,默认8
严格模式
set hive.mapred.mode=strict; --设置严格模式
情况1:分区表,必须含有对分区字段的where过滤条件
情况2:order by,对于使用order by的查询,必须使用limit
情况3:笛卡尔,避免使用笛卡尔查询
JVM重用(慎用)
set mapred.job.reuse.jvm.num.tasks=10; --设置JVM实例在同一个job中重复使用10次,通常在10-20之间
情况1:很难避免小文件
情况2:task特别多
推测执行(慎用)
如果输入数据量很大需要执行长时间的map或者redue,推测执行会造成很大资源浪费。
压缩
Hive表中间数据压缩
set hive.exec.compress.intermediate=true; --开启中间数据压缩功能,默认false
set mapreduce.map.output.compress=true; --开启mapreduce中map输出压缩功能,默认false
set mapred.map.output.compression.codec= org.apache.hadoop.io.compress.SnappyCodec; --设置中间数据的压缩算法
Hive表最终输出压缩
set hive.exec.compress.output=true; --开启输出结果压缩功能
set mapred.output.compression.codec= org.apache.hadoop.io.compress.SnappyCodec; --设置输出结果压缩算法