Hive 性能优化

影响 Hive 效率的几乎从不是数据量过大,而是数据倾斜、数据冗余、job 或 I/O 过多、MapReduce 分配不合理等等。

对 Hive 的调优主要包括三方面:

  • HQL 语句调优
  • Hive 配置项调优
  • MapReduce 调优
1. 列裁剪和分区裁剪

所谓列裁剪就是在查询时只读取需要的列,分区裁剪就是只读取需要的分区

select uid,event_type,record_data
from calendar_record_log
where pt_date >= 20190201 and pt_date <= 20190224
and status = 0;

当列很多或者数据量很大时,如果 select * 或者不指定分区,全列扫描和全表扫描效率都很低。
Hive 中与列裁剪优化相关的配置项是hive.optimize.cp,与分区裁剪优化相关的则是hive.optimize.pruner,默认都是true。

2. 谓词下推

它就是将 SQL 语句中的 where 谓词逻辑都尽可能提前执行,减少下游处理的数据量。

3. sort by代替order by

HiveQL中的order by与其他SQL方言中的功能一样,就是将结果按某字段全局排序,这会导致所有map端数据都进入一个reducer中,在数据量大时可能会长时间计算不完。
如果使用sort by,那么还是会视情况启动多个reducer进行排序,并且保证每个reducer内局部有序。为了控制map端数据分配到reducer的key,往往还要配合distribute by一同使用。如果不加distribute by的话,map端数据就会随机分配到reducer。
举个例子,假如要以UID为key,以上传时间倒序、记录类型倒序输出记录数据:

select uid,upload_time,event_type,record_data
from calendar_record_log
where pt_date >= 20190201 and pt_date <= 20190224
distribute by uid
sort by upload_time desc,event_type desc;
4. left semi join 代替 in/exists

虽然 Hive 也支持 in/exists 操作,但还是推荐使用 Hive 的一个高效替代方案:left semi join

select a.id, a.name from a where a.id in (select b.id from b);
select a.id, a.name from a where exists (select id from b where a.id = b.id);

# 应该替换为
select a.id, a.name from a left semi join b on a.id = b.id;
5. 设置合理的 MapTask 数量
  • MapTask 数量过大:Map 阶段输出文件太小,产生大量小文件;初始化和创建 Map 的开销很大
  • MapTask 数量过小:文件处理或查询并发度小,Job 执行时间过长;•大量作业时,容易堵塞集群

控制 MapTask 的数量方案:

  • 减少 MapTask 数量是通过合并小文件来实现(这一点主要是针对数据源)
  • 增加 MapTask 数量可以通过控制上一个 Job 的 reduceTask 个数
6. 小文件合并

文件数目过多,会给 HDFS 带来压力,并且会影响处理效率,可以通过合并 Map 和 Reduce 的 结果文件来消除这样的影响:

以下是 Hive 小文件合并相关的配置项:

set hive.merge.mapfiles = true ##在 map only 的任务结束时合并小文件
set hive.merge.mapredfiles = false ## true 时在 MapReduce 的任务结束时合并小文件
set hive.merge.size.per.task = 256*1000*1000 ##合并文件的大小
set mapred.max.split.size=256000000; ##每个 Map 最大分割大小
set mapred.min.split.size.per.node=1; ##一个节点上 split 的最少值
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat; ##执行 Map 前进行小文件合
7. 设置合理的 ReduceTask 的数量

Hadoop MapReduce 程序中,ReduceTask 个数的设定极大影响执行效率,这使得 Hive 怎样决定 reducer 个数成为一个关键问题。

遗憾的是 Hive 的估计机制很弱,不指定 ReduceTask 个数的情况下,Hive 会猜测确定一个 ReduceTask 个数,基于以下设定:

  • hive.exec.reducers.bytes.per.reducer(默认为 256000000)
  • hive.exec.reducers.max(默认为 1009)
  • mapreduce.job.reduces=-1(设置一个常量 reducetask 数量)

计算 ReduceTask 数量公式:N=min(参数 2,总输入数据量/参数 1)

依据 Hadoop 的经验,建议将参数 2 设定为 0.95*(集群中 datanode 个数)

8. 合理利用 partition(分区) 和 bucket(分桶)
  • Partition 就是分区。分区通过在创建表时启用 partitioned by 实现,用来 partition 的维度并不 是实际数据的某一列,具体分区的标志是由插入内容时给定的。当要查询某一分区的内容时可以采用 where 语句,形似 where tablename.partition_column = a 来实现。

  • Bucket 是指将数据以指定列的值为 key 进行 hash,hash 到指定数目的桶中。这样就可以支 持高效采样了

9. Join 优化

总体原则:

  1. 优先过滤后再进行 Join 操作,最大限度的减少参与 Join 的数据量
  2. 小表 Join 大表,最好启动 MapJoin
  3. Join on 的条件相同的话,最好放入同一个 Job,并且 Join 表的排列顺序从小到大

在使用写有 Join 操作的查询语句时有一条原则:应该将条目少的表/子查询放在 Join 操作符的左边

10. Group By 优化

并不是所有的聚合操作都需要在 Reduce 端完成,很多聚合操作都可以先在 Map 端进行部分聚合,最后在 Reduce 端得出最终结果。

MapReduce 的 combiner 组件参数包括:

  • set hive.map.aggr = true #是否在 Map 端进行聚合,默认为 True
  • set hive.groupby.mapaggr.checkinterval = 100000 #在 Map 端进行聚合操作的条目数目
11. 合理利用文件存储格式

创建表时,尽量使用 orc、parquet 这些列式存储格式,因为列式存储的表,每一列的数据在物理上是存储在一起的,Hive 查询时会只遍历需要列数据,大大减少处理的数据量。

12. 本地模式执行 MapReduce

Hive 在集群上查询时,默认是在集群上 N 台机器上运行, 需要多个机器进行协调运行,这个方式很好地解决了大数据量的查询问题。

但是当 Hive 查询处理的数据量比较小时,其实没有必要启动分布式模式去执行,因为以分布式方式执行就涉及到跨网络传输、多节点协调等,并且消耗资源。这个时间可以只使用本地模式来执行 mapreduce job,只在一台机器上 执行,速度会很快。

启动本地模式涉及到三个参数:

  • set hive.exec.mode.local.auto=true:让 Hive 决定是否本地模式运行
  • hive.exec.mode.local.auto.input.files.max:不启用本地模式的 Task 最大个数
  • hive.exec.mode.local.auto.inputbytes.max:不启动本地模式的最大输入文件大小
13. 并行化处理

一个 Hive SQL 语句可能会转为多个 mapreduce Job,每一个 job 就是一个 stage,这些 job 顺序 执行,这个在 cli 的运行日志中也可以看到。

但是有时候这些任务之间并不是是相互依赖的, 如果集群资源允许的话,可以让多个并不相互依赖 stage 并发执行,这样就节约了时间,提 高了执行速度,但是如果集群资源匮乏时,启用并行化反倒是会导致各个 job 相互抢占资源 而导致整体执行性能的下降。

启用并行化:

set hive.exec.parallel=true; set hive.exec.parallel.thread.number=8; // 同一个 sql 允许并行任务的最大线程数

14. 设置压缩存储

Hive 最终是转为 MapReduce 程序来执行的,而 MapReduce 的性能瓶颈在于网络 IO 和 磁盘 IO,要解决性能瓶颈,最主要的是减少数据量,对数据进行压缩是个好的方式。

压缩虽然是减少了数据量,但是压缩过程要消耗 CPU 的,但是在 Hadoop 中, 往往性能瓶颈不在于 CPU,CPU 压力并不大,所以压缩充分利用了比较空闲的 CPU。

Job 输出文件按照 block 以 GZip 的方式进行压缩:

set mapreduce.output.fileoutputformat.compress=true // 默认值是 false
set mapreduce.output.fileoutputformat.compress.type=BLOCK // 默认值是 Record
set mapreduce.output.fileoutputformat.compress.codec=org.apache.hadoop.io.compress.GzipCodec // 默认值是 org.apache.hadoop.io.compress.DefaultCodec

Map 输出结果也以 Gzip 进行压缩:

set mapred.map.output.compress=true
set mapreduce.map.output.compress.codec=org.apache.hadoop.io.compress.GzipCodec // 默认值是 org.apache.hadoop.io.compress.DefaultCodec 

对 Hive 输出结果和中间都进行压缩

set hive.exec.compress.output=true // 默认值是 false,不压缩
set hive.exec.compress.intermediate=true // 默认值是 false,为 true 时 MR 设置的压缩才启用
posted @ 2021-12-19 22:59  追こするれい的人  阅读(162)  评论(0编辑  收藏  举报