个人觉得Hive的调优,最最重要的是理解业务,写出符合业务的最优的SQL语句,然后才是对SQL的执行过程进行优化。本文主要是对截止目前为止个人对HSQL调优的一些简单的经验总结,其中底层执行引擎为MapReduce。HSQL的执行就是将SQL翻译成多个MapReduce作业来执行,具体划分橙多个个MapReduce任务以及每个MapReduce人物的内容可以使用explian命令查看,这个可以查看我之前写的文章《Hive执行计划初步了解》。
个人觉得HSQL任务调优主要分为三个方面:
1. SQL语句的调优
2. Hive表存储的调优
3. MR任务的调优
SQL语句的调优:
写好SQL语句是调优的关键,但是本人HSQL其实写的并不多...所以在如何编写高效的SQL语句方面并没有很深的见解,目前个人觉得有如下几点需要注意的地方:
1. SQL中的常量尽量提前计算,而不是在SQL语句中进行计算。
例如: where timestamp > to_unix_timestamp(“2021-02-14”, "yyyy-MM-dd") 可以写成 where timestamp > 1613232000
2. 使用group by 代替 distinct / distinct(count *)。 distinct自身的机制决定了只会出现一个reduce Task,导致reduce阶段任务并行度不足,而group by却可以分布式执行
3. select 具体列,而不是select *
4. 尽量在join之前过滤不必要的数据,减少要join的数据量
......关于SQL语句方面的一些调优,等到我写足够多的SQL之后再说吧,目前这方面不敢妄言,哈哈哈
Hive表存储相关调优:
表存储相关调优主要是指表的存储格式以及压缩方式的选择。表存储优化这块主要是两点:文件能被快速读取 和 规避小文件
快速读取文件:
Hive表底层的数据是以文件的形式存储在HDFS上的,HSQL底层又是基于MapReduce程序来执行,而MapReduce 的性能瓶颈在于网络IO和磁盘IO,所以文件选择合适的压缩格式和存储格式非常重要。
Hive数据存储支持的格式有文本格式(TextFile)、二进制序列化文件 (SequenceFile)、行列式文件(RCFile)、Apache Parquet和优化的行列式文件(ORCFile)。
TextFile存储空间消耗比较大,并且压缩过的TextFile无法分割和合并,虽然加载数据的速度最高,但是查询效率最低。SequenceFile查询效率较高,但是存储空间消耗最大。由于ORCFile和Apache Parquet有着高效的数据存储和数据处理性能,所以生产环境中一般都会选取这两种数据存储格式进行数据的存储。
同时Hive还支持多种压缩算法,对文件进行压缩。Hive压缩格式支持Gzip、Bzip2、lzo、snappy,可以根据业务情况选取压缩方式(要注意下文件是否可分割,可分割的文件可以扩展Map Task的数量,这个后面回讲到):
规避小文件:
小文件过多有两大危害:第一点是MR作业中Map Task的数量和文件个数相关,太多的小文件会导致启动太多的Map Task,导致资源的浪费(一个Map Task对应一个JVM进程);第二点是在HDFS中,每个小文件对象约占150byte,如果小文件过多会占用大量NameNode的内存,严重影响HDFS集群的稳定性;所以一定要想办法规避小文件
小文件怎么来的?:
1. 动态分区插入数据时会产生大量的小文件。
2. Reduce数量越多,小文件也越多(Reduce的个数和输出文件数是对应的)。
3. 数据源本身就包含大量的小文件
其中1和2算是MR任务主动生成的小文件,3算是MR任务被动读取小文件
小文件解决方案如下:
从小文件产生的途经就可以从源头上控制小文件数量,方法如下:
1. 少用动态分区,而是使用distribute by分区
2. 减少reduce的数量,即调整MapReduce任务Reduce相关参数进行控制,下面会讲到。
3. 如果数据源本身就包含大量小文件,那么就得调整MapReduce任务Map相关参数进行控制,下面会讲到。
设置Map合并输入的小文件相关参数:
//执行Map前进行小文件合并
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
//每个Map最大输入大小(这个值决定了合并后文件的数量)
set mapred.max.split.size=256000000;
//一个节点上split的至少的大小(这个值决定了多个DataNode上的文件是否需要合并)
set mapred.min.split.size.per.node=100000000;
//一个交换机下split的至少的大小(这个值决定了多个交换机上的文件是否需要合并)
set mapred.min.split.size.per.rack=100000000;
设置Reduce Task个数相关参数:
//设置每个reduce任务处理的数据量(建议设置这个参数)
set hive.exec.reducers.bytes.per.reducer=500000000
;
//指定reduce任务个数(不建议设置这个值)
set mapred.reduce.tasks
=15;
设置Map输出和Reduce输出进行合并的相关参数:
//设置map端输出进行合并,默认为true
set hive.merge.mapfiles = true
//设置reduce端输出进行合并,默认为false
set hive.merge.mapredfiles = true
//设置合并文件的大小
set hive.merge.size.per.task = 256*1000*1000
//当输出文件的平均大小小于该值时,启动一个独立的MapReduce任务进行文件merge。
set hive.merge.smallfiles.avgsize = 16000000
MapReduce任务的调优:
//1. 开始SQL任务的并行化执行
set hive.exec.parallel = true
一个SQL会被解析成多个Map/Reduce作业,这些作业有时候是相互之间不关联的,比如: select * from A UNION ALL select * from B。如果这个时候设置了并行化执行,那么select A和select B遍能同时执行,大大加快了任务的执行速度,但是这样会消耗更多的资源。
//2. 开启向量化读
set hive.vectorized.execution.enabled = true
通过一次性读取多行而不是单行来执行SQL,从而提高扫描,聚合,过滤等操作,从Hive0.13.0中引入,大大提高了查询执行时间。不过好像只支持ORC和Parquet。
//3. 开启JVM重用
set mapred.job.reuse.jvm.num.tasks = 10
每个map和reduce task都会对应启动一个JVM,这会造成相当大的开销,尤其是执行的job包含有成千上万个task任务的情况。
一些小文件的场景或者task特别多的场景,每个task执行时间都很短。这个时候建议开启JVM重用,使得JVM实例在同一个JOB中重新使用N次。
//4. 开启Map join
set hive.auto.convert.join = true
其他调优还有数据倾斜、文件压缩、推测执行、Shuffle等相关调优,这些尚未完全理解,后续看明白了再说吧...如有发现错误,麻烦帮忙指出下哈
参考:
《Hive性能调优实战》
https://www.jianshu.com/p/08abf1f25ea4(Hive执行机制)
https://blog.csdn.net/lzm1340458776/article/details/43567209/(Hive小文件数量的危害)
https://blog.csdn.net/aijiudu/article/details/72353510(MapReduce过程详解及其性能优化)
https://www.cnblogs.com/sx66/p/12039248.html(Hive支持的数据存储格式)
http://lxw1234.com/archives/2015/04/15.htm(控制Map和Reduce task个数)