以下内容整理自学习资料和自己平时实践中遇见的一些问题和经验小结

常用优化手段

•查询时只读取需要的列和分区

•将where等筛选条件尽可能提前执行,减少下游处理的数据量

•谨慎使用order by,count(distinct)这种会导致所有map端数据都进入一个reducer的操作

•数据量大时去重使用group by而不是distinct,但数据量较小时group by效率可能会低于distinct

•避免内存溢出

        将条目少的查询放在 Join的左边。 原因是在 Join 操作的 Reduce 阶段,位于 Join左边的表的内容会被加载进内存,将条目少的表放在左边,可以有效减少发生内存溢出的几率。

•解决数据倾斜问题,多发生于数据在节点上分布不均匀,join时左边的表key分布比较集中(空值较多),count(distinct)字段存在大量值为NULL或空的记录等场景

        1. 参数控制 hive.groupby.skewindata =true。

        2. 在key前面加上一个随机数,范围为[0,reducer数量),把倾斜的数据分到不同的reduce上。

        3. 当一个小表关联一个超大表时,容易发生数据倾斜,可以用MapJoin把小表全部加载到内存在map端进行join,避免reducer处理,hive.auto.convert.join = true可以自动选择小表Mapjoin。

        4. 将空值key用随机方式打散,例如将用户ID为null的记录随机改为负值

•控制合理的map reduce的task数,能有效提升性能

        1. 减少map数可以通过合并小文件来实现,增加map数的可以通过控制上一个job的reduer数来控制。

        2. 不指定reducer个数的情况下,Hive分配reducer个数基于参数1:hive.exec.reducers.bytes.per.reducer(默认为1G),参数2 :hive.exec.reducers.max(默认为999),计算reducer数的公式  N=min(参数2,总输入数据量/参数1),也可以利用参数指定set mapred.reduce.tasks=13。

•减少job数(Multi-Insert,Multi-group by),尽量避免笛卡尔积

实践中比较常用到的一些参数

#常用
 set hive.execution.engine=mr
 set mapred.map.tasks=20
 set mapred.reduce.tasks=36
 set hive.exec.parallel=true
 set hive.auto.convert.join=false 
 set hive.groupby.skewindata=true 
 set hive.hadoop.supports.splittable.combineinputformat=true
 set mapreduce.input.fileinputformat.split.maxsize=512000000
 set mapreduce.input.fileinputformat.split.minsize.per.node=512000000
 set mapreduce.input.fileinputformat.split.minsize.per.rack=512000000
 set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat #执行Map前进行小

文件合并

set hive.merge.mapfiles=true #在Map-only的任务结束时合并小文件
 set hive.merge.smallfiles.avgsize=256000000
 set hive.exec.compress.intermediate=true
 set hive.exec.compress.output=true
 set hive.intermediate.compression.codec=org.apache.hadoop.io.compress.SnappyCodec
 set mapred.output.compression.codec=org.apache.hadoop.io.compress.SnappyCodec#数据分桶优化
 set hive.enforce.bucketing=true
 set hive.input.format=org.apache.hadoop.hive.ql.io.BucketizedHiveInputFormat
 set hive.optimize.bucketmapjoin=true
 set hive.optimize.bucketmapjoin.sortedmerge=true#大小表Join优化
 hive.mapjoin.smalltable.filesize=25000000 #大表小表的阀值
 hive.mapjoin.cache.numrows #mapjoin 存在内存里的数据量 
 hive.mapjoin.followby.gby.localtask.max.memory.usage #map join做group by 操作时,可以使用多大的内存来存储数据,如果数据太大,则不会保存在内存里
 hive.mapjoin.localtask.max.memory.usage hive.mapjoin.localtask.max.memory.usage#小文件合并优化
 set mapred.map.tasks=20; 
 set hive.merge.mapfiles = true #在Map-only的任务结束时合并小文件
 set hive.merge.mapredfiles = true #默认是false,true时在Map-Reduce的任务结束时合并小文件
 set hive.merge.size.per.task = 256*1000*1000 #合并文件的大小
 set mapred.max.split.size=256000000;  #每个Map最大分割大小
 set mapred.min.split.size.per.node=100000000; #一个节点上split的最少值,决定了多个data node上的文件是否需要合并~
 set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;  #执行Map前进行小文件合并#JVM重用
 set mapred.job.reuse.jvm.num.tasks=5;


在MR job中,默认是每执行一个task就启动一个JVM,例如将这个参数设成5,那么就代表同一个MR job中顺序执行的5个task可以重复使用一个JVM,减少启动和关闭的开销。但它对不同MR job中的task无效

 

常见案例

案例1

向tmp_content表中按照t_when字段导入对应分区时,生成的MR任务数量太少导致作业卡住,通过下列参数可以进行拆分然后就可以正常导入了。

set hive.exec.dynamici.partition=true;
 set hive.exec.dynamic.partition.mode=nonstrict;
 SET hive.exec.max.dynamic.partitions=2048;
 SET hive.exec.max.dynamic.partitions.pernode=1000;
 set hive.exec.reducers.max=500;
 set hive.exec.max.created.files=500000;  
 set mapred.reduce.tasks =20000;  
 set hive.merge.mapfiles=true;insert overwrite table tmp_content partition(dt)
 select msg_id,account_id,t_when,substr(t_when,1,8) from tmp_content_his DISTRIBUTE BY rand();

案例2

任务中不管数据量多大,不管有没有设置调整reduce个数的参数,任务中一直都只有一个reduce任务可能原因:

1. 除了数据量小于hive.exec.reducers.bytes.per.reducer参数值的情况外

2. 没有group by的汇总

3. 用了Order by

案例3

环境如下:

hive.merge.mapredfiles=true (默认是false,可以在hive-site.xml里配置)
hive.merge.mapfiles=true
hive.merge.size.per.task=256000000
mapred.map.tasks=2

因为合并小文件默认为true,而dfs.block.size与hive.merge.size.per.task的搭配使得合并后的绝大部分文件都在256MB左右。

Case 1:

现在我们假设有3个300MB大小的文件,

整个JOB会有6个map,其中3个map分别处理256MB的数据,还有3个map分别处理44MB的数据。

木桶效应就来了,整个JOB的map阶段的执行时间不是看最短的1个map的执行时间,而是看最长的1个map的执行时间。所以,虽然有3个map分别只处理44MB的数据,可以很快跑完,但它们还是要等待另外3个处理256MB的map。显然,处理256MB的3个map拖了整个JOB的后腿。

Case 2:

如果我们把mapred.map.tasks设置成6,再来看一下有什么变化:

goalsize = min(900MB/6,256MB) = 150MB

整个JOB同样会分配6个map来处理,每个map处理150MB的数据,非常均匀,谁都不会拖后腿,最合理地分配了资源,执行时间大约为CASE 1的59%(150/256)