在讨论hive优化之前,我们需要知道的是HQL它的执行过程。
简单的说,HQL会最终转化为job,然后通过MR来执行job
问题一 既然HQL会转化为JOB,那么如果job数量太多,会不会对hive执行带来性能的影响?
我们知道客户端提交JOB到YARN集群,然后MRAppMaster则会创建JOB,并对JOB进行初始化,初始化JOB是需要好费时间的,因为在这里会根据文件创建分片,然后决定Mapper数量等,还要创建MapTask和ReduceTask,如果JOB太多,肯定会对hive性能有影响的
YARN在初始化JOB的时候,是需要耗时的
问题二 既然最终是通过MR来运行JOB,那么MR本身是否存在着隐式的性能问题,比如Map数量太多或者Reduce数量太多会不会带来影响,数据倾斜怎么办?
Mapper数量太多:会产生大量的文件,如果这些文件还是小文件,那么就有调优的可能,因为我们知道创建和初始化和启动Mapper是需要很大开销的,一个Mapper任务就是一个jvm进程。
还有个问题就是Mapper数量太多,那么Suffle过程所耗费的网络1/O和磁盘I/O很大,还会涉及到排序,所以Mapper数量过多并不是一个好事
Mapper数量太太少:如果文件过大,Mapper数量太少,那么文件处理的并发度肯定就不好,JOB需要执行的时间就长。
Reducer数量太多:我们知道一个Reducer就对应一个输出,如果这些是小文件,那么其他job依赖这个job的结果,那么又是很多小文件;另外初始化和启动Reducer同Mapper一样,也会有很大的开销
Reducer数量太少: 如果只需跑Map任务,没啥影响,如果数据量很大,Reducer数量太少,并发度不高,执行耗时;而且数据倾斜的可能性很大
那我们带着问题来探讨hive的优化过程及方式。
一 设计层面的优化措施
一般而言,我们使用Hive的时候都会预先估计到hive的数据量大概有多少,如果是大量的数据的话,我们应该提前在设计或者架构层面做好一些优化措施:
1.1分区
合理设计表分区,是一个很不错的优化措施,因为这样,我们就可以避免全表扫描,而在过滤的时候只针对对应分区的数据扫描。
1.2使用列式存储格式和开启压缩
一般来讲,列式存储我们在查询的时候,可以跳过不必要查询的列,不会把所有列都查询出来,并且他自己有着高效的压缩率,既节省空间也节省内存和CPU
1.3尽量使用宽表代表嵌套表
1.4如果有JOIN场景,应该考虑表的具体情况,选择合适的JOJN方式:
如果都是小表:可以使用普通的JOIN方式,即ReduceSide JOIN
如果一个是小表,一个是大表:
理论上小表取决于hive.mapjoin.smalltable.filesize设置的值为准,取决于自己的业务也可以自己设置这个值,我们是可以只进行MapSide Join,而不用去做Reduce,这样就可以节省shuffle过程的耗费资源的操作,这个一般小表放在JOIN左边,大表放在右边,因为会把左边的表读取出来放到内存,然后通过DistributedCache缓存到各个节点
如果都是大表:那么对表我们可以进行合适的分桶,2张表都需要对参与JOIN的key进行分桶,桶数可以一样,也可以不一样,但是不一样也是有前提的,大表需要是小表的倍数,即必须保证他们相同的key都分到相同的桶里面去了
另外JOIN一定要避免笛卡尔积,在hive中,数据量有很大的情况下,这可能回带来灾难
注意:表连接不是小表数据量少就放在JOIN左边,而是说应该JOIN的key重复值少的应该放在左边
二 配置参数的优化
2.1设置合理的mapper数量和reducer数量
如果表很大的话,我们需要设置合理的mapper数量和reducer数量,避免设置太小,没有合理利用资源,执行时间慢;设置太大,过度消耗资源
2.1.1mapper: 很多都是小文件
我们知道一个小文件,默认小于128M,就是一个切片,就会产生一个map task。那如果小文件太多,则会造成很多的maptask;而且还会对NameNode带来压力
解决办法:合并小文件,我们可以在执行前设置这个参数:
sethive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInput
Format;
2.1.2mapper: 如果表本身就很大
如果文件本身就很大,我么可以改变mapper数量,在产生切片的时候,
我们可以调整切片的数量,进而调整mapper的数量。
每个map处理的最大的文件大小
setmapred.max.split.size=256000000;
一个节点上split的最小是多少,默认是1字节 ,决定了多个datanode上的文件是否需要合并
setmapred.min.split.size.per.node=100000000;
一个交换机下split的最小是多少,默认是1字节,决定了多个交换机上的文件是否需要合并
setmapred.min.split.size.per.rack=100000000;
注意:mapred.min.split.size.per.node和mapred.min.split.size.per.rack会带来一定的I/O消耗,因为需要合并文件
2.1.3Reducer 个数太多或者太少:
控制最大的reducer数量, 默认为999
hive.exec.reducers.max
这个参数控制一个job会有多少个reducer来处理,依据的是输入文件的总大小.默认1GB。
hive.exec.reducers.bytes.per.reducer
直接指定Reduce个数,如果指定hive就不会自己计算需要的Reducer个数
mapred.reduce.tasks
hive分配reducer个数的算法:
hive.exec.reducers.bytes.per.reducer(默认为1G)
hive.exec.reducers.max(默认为999)
min(hive.exec.reducers.max,输入文件大小/ hive.exec.reducers.bytes.per.
reducer)取这两个值中间最小的
一旦设置了mapred.reduce.tasks这个参数,那么上述算法无效,优先使用这个值的参数,默认这个值是-1
有一些情况,无论我们怎么调整,始终只有一个reduder:
#文件数据量太小,没有达到hive.exec.reducers.bytes.per.reducer这个标准
#使用了groupby然后进行汇总的功能
#使用orderby全局排序(尽量使用SORT BY,SORT BY先在每一个Reducer做局部排序,最后再起一个JOB对排好序的进行全局排序)
2.2map端聚合
如果为了减轻reduce端进行聚合操作的压力,我们可以在map端就进行一次reduce,也就是shuffle过程的combine操作
hive.map.aggr=true.默认就是开启的
三 对HQL语句的优化
有些时候,我们是可以针对HQL语句进行优化的。
比如我们只是查询我们需要的列等,这个就得具体问题具体分析
四 数据倾斜
什么是数据倾斜?
在MapReduce计算的时候,某一个key的值过度集中,导致Reduce端负载不均衡,有的Reduce很快就完看,有的Reduce要运行很长时间才能完成。一般这种情况发生在JOIN或者GROUPBY的时候
4.1GROUP BY 数据倾斜
解决办法:
第一种: 在map 端就进行combine,先对key进行一次聚合,这样,Reducer取数据的时候,就会减少很多。而且Reducer的时候参与计算的key也少很多
第二种:配置参数hive.groupby.skewindata= true
这是hive通用的数据倾斜优化方案,但是还是得结合具体的业务而定,并不一定就适合你的场景。
他的原理就是先启动一个JOB, 不按照key进行分区,然后随机分给reducer,每一个Reducer来做部分聚合操作,这样相同的groupkey就有可能分发到不同的Reducer之中,从而达到负载均衡的目的;然后再起一个JOB, 根据上一个JOB的Reduce的输出,然后按照groupkey进行聚合操作,最后完成最终的聚合操作
这样做效果也不是很明显,不推荐使用。
第三种:改写HQL语句
/*改写前*/
selecta,count(distinct b) as c from tbl group by a;
/*改写后*/
selecta,count(*) as c
from(select distinct a, b from tbl) group by a;
4.2JOIN造成的数据倾斜
第一种:hive自身的优化方案skewjoin
原理就是把JOINkey加上一些随机数,将这些key打乱,然后就会在不同的节点计算;最后在启动一个JOB,以第一个作为输入源,将相同的key分发到相同的节点上处理
必要条件:
sethive.optimize.skewjoin = true;
key要达到多少才会认为是倾斜数据,然后才进行优化
sethive.skewjoin.key=1000000;
4.3JOIN key数据类型不同也会造成数据倾斜
将类型通过函数转换一致
其他优化案例:
Count(distinct)
l 当该字段存在大量值为null或空的记录时容易造成倾斜。
解决思路:
1) count(distinct)时,将值为空的数据在where里过滤掉,在最后结果中加1。
2) 如果还有其他计算,需要进行groupby,可以先将值为空的记录单独处理,再和其他计算结果进行union。
3) 如果groupby维度过小,则可以 采用count和groupby的方式来替换count(distinct)完成计算
l 特殊情况特殊处理:
在业务逻辑优化效果不大情况下,有些时候是可以将倾斜的数据单独拿出来处理,最后union回去。
l countdistinct优化:
实例1:
优化前:
selectcount(distinct id) from student;
只有一个job任务,而且只有一个reduce,处理的工作量比较大。
优化后:
selectcount(1) from (select distinct id from student) tmp;
或
selectcount(1) from (select id from student group by id) tmp;
可以通过设置setmapred.reduce.tasks的值,加快(select distinct id from student) tmp部分的处理。
实例2:
优化前:
selecta,sum(b),count(distinctc),count(distinct d)
fromtest
groupby a;
优化后:
selecta,sum(b)as b,count(c) as c,count(d) as d
from(
selecta,0 as b,c,null as d from test group by a,c
unionall
selecta,0 as b,null as c,d from test group by a,d
unionall
selecta,b,null as c,null as d from test
)tmpgroup bya;
我们知道文件数目小,容易在文件存储端造成瓶颈,给 HDFS 带来压力,影响处理效率。对此,可以通过合并Map和Reduce的结果文件来消除这样的影响。
用于设置合并属性的参数有:
是否合并Map输出文件:hive.merge.mapfiles=true(默认值为真)
是否合并Reduce 端输出文件:hive.merge.mapredfiles=false(默认值为假)
合并文件的大小:hive.merge.size.per.task=256*1000*1000(默认值为256000000)