Hive优化

一、join优化

1. 使用相同的连接键
当对3个或者更多个表进行join连接时,如果每个on子句都使用相同的连接键的话,那么只会产生一个MapReduce job。

2. 尽量尽早地过滤数据
减少每个阶段的数据量,对于分区表要加分区,同时只选择需要使用到的字段。

3. 尽量原子化操作
尽量避免一个SQL包含复杂逻辑,可以使用中间表来完成复杂的逻辑。

二、小文件优化

1.小文件过多产生的影响
首先对底层存储HDFS来说,HDFS本身就不适合存储大量小文件,小文件过多会导致namenode元数据特别大, 占用太多内存,严重影响HDFS的性能
对 Hive 来说,在进行查询时,每个小文件都会当成一个块,启动一个Map任务来完成,而一个Map任务启动和初始化的时间远远大于逻辑处理的时间,就会造成很大的资源浪费。而且,同时可执行的Map数量是受限的
2.怎么解决小文件过多
1)使用 hive 自带的 concatenate 命令,自动合并小文件

#对于非分区表
alter table A concatenate;
#对于分区表
alter table B partition(day=20201224) concatenate;

2)调整参数减少Map数量

#执行Map前进行小文件合并
#CombineHiveInputFormat底层是 Hadoop的 CombineFileInputFormat 方法
#此方法是在mapper中将多个文件合成一个split作为输入
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat; -- 默认

#每个Map最大输入大小(这个值决定了合并后文件的数量)
set mapred.max.split.size=256000000;   -- 256M

#一个节点上split的至少的大小(这个值决定了多个DataNode上的文件是否需要合并)
set mapred.min.split.size.per.node=100000000;  -- 100M

#一个交换机下split的至少的大小(这个值决定了多个交换机上的文件是否需要合并)
set mapred.min.split.size.per.rack=100000000;  -- 100M

#增大文件大小为2G,减少map个数
set mapreduce.input.fileinputformat.split.maxsize=2048000000;

设置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;   -- 256M

#当输出文件的平均大小小于该值时,启动一个独立的MapReduce任务进行文件merge
set hive.merge.smallfiles.avgsize=16000000;   -- 16M 

启用压缩

# hive的查询结果输出是否进行压缩
set hive.exec.compress.output=true;

# MapReduce Job的结果输出是否使用压缩
set mapreduce.output.fileoutputformat.compress=true;

减少reduce数量

#reduce 的个数决定了输出的文件的个数,所以可以调整reduce的个数控制hive表的文件数量,
#hive中的分区函数 distribute by 正好是控制MR中partition分区的,
#然后通过设置reduce的数量,结合分区函数让数据均衡的进入每个reduce即可。

#设置reduce的数量有两种方式,第一种是直接设置reduce个数
set mapreduce.job.reduces=10;

#第二种是设置每个reduce的大小,Hive会根据数据总大小猜测确定一个reduce个数
set hive.exec.reducers.bytes.per.reducer=5120000000; -- 默认是1G,设置为5G

#执行以下语句,将数据均衡的分配到reduce中
set mapreduce.job.reduces=10;
insert overwrite table A partition(dt)
select * from B
distribute by rand();

解释:如设置reduce数量为10,则使用 rand(), 随机生成一个数 x % 10 ,
这样数据就会随机进入 reduce 中,防止出现有的文件过大或过小

使用hadoop的archive将小文件归档

#用来控制归档是否可用
set hive.archive.enabled=true;
#通知Hive在创建归档时是否可以设置父目录
set hive.archive.har.parentdir.settable=true;
#控制需要归档文件的大小
set har.partfile.size=1099511627776;

#使用以下命令进行归档
ALTER TABLE A ARCHIVE PARTITION(dt='2022-02-24', hr='12');

#对已归档的分区恢复为原文件
ALTER TABLE A UNARCHIVE PARTITION(dt='2022-02-24', hr='12');

设置广播表大小

hive.mapjoin.smalltable.filesize=25000000;

三、strict模式

开启严格模式对分区表进行查询,在where子句中没有加分区过滤的话,将禁止提交任务(默认:nonstrict)

set hive.mapred.mode=strict 开启严格模式

注:使用严格模式可以禁止以下三种类型的查询:

1. 对分区表的查询必须使用到分区相关的字段
分区表的数据量通常都比较大,对分区表的查询必须使用到分区相关的字段,不允许扫描所有分区,想想也是如果扫描所有分区的话那么对表进行分区还有什么意义呢。

当然某些特殊情况可能还是需要扫描所有分区,这个时候就需要记得确保严格模式被关闭。

2. order by必须带limit
因为要保证全局有序需要将所有的数据拉到一个Reducer上,当数据集比较大时速度会很慢。个人猜测可能是设置了limit N之后就会有一个很简单的优化算法:每个Reducer排序取N然后再合并排序取N即可,可大大减少数据传输量。

3. 禁止笛卡尔积查询(join必须有on连接条件)
Hive不会对where中的连接条件优化为on,所以join必须带有on连接条件,不允许两个表直接相乘。

四、并行执行优化

Hive会将一个查询转化成一个或者多个阶段。这样的阶段可以是MapReduce阶段、抽样阶段、合并阶段、limit阶段。或者Hive执行过程中可能需要的其他阶段。默认情况下,Hive一次只会执行一个阶段。不过,某个特定的job可能包含众多的阶段,而这些阶段可能并非完全互相依赖的,也就是说有些阶段是可以并行执行的,这样可能使得整个job的执行时间缩短。如果有更多的阶段可以并行执行,那么job可能就越快完成。

通过设置参数hive.exec.parallel值为true,就可以开启并发执行。在共享集群中,需要注意下,如果job中并行阶段增多,那么集群利用率就会增加。

set hive.exec.parallel=true; //打开任务并行执行
set hive.exec.parallel.thread.number=16; //同一个sql允许最大并行度,默认为8。

五、推测执行优化

在分布式集群环境下,因为程序bug(包括Hadoop本身的bug),负载不均衡或者资源分布不均等原因,会造成同一个作业的多个任务之间运行速度不一致,有些任务的运行速度可能明显慢于其他任务(比如一个作业的某个任务进度只有50%,而其他所有任务已经运行完毕),则这些任务会拖慢作业的整体执行进度。为了避免这种情况发生,Hadoop采用了推测执行(Speculative Execution)机制,它根据一定的法则推测出“拖后腿”的任务,并为这样的任务启动一个备份任务,让该任务与原始任务同时处理同一份数据,并最终选用最先成功运行完成任务的计算结果作为最终结果。

设置开启推测执行参数:Hadoop的mapred-site.xml文件中进行配置:

<property>
  <name>mapreduce.map.speculative</name>
  <value>true</value>
  <description>If true, then multiple instances of some map tasks 
               may be executed in parallel.</description>
</property>

<property>
  <name>mapreduce.reduce.speculative</name>
  <value>true</value>
  <description>If true, then multiple instances of some reduce tasks 
               may be executed in parallel.</description>
</property>

任务级别:set hive.mapred.reduce.tasks.speculative.execution=true

六、数据倾斜优化

set hive.map.aggr=true;  
set hive.groupby.skewindata = ture;

当选项设定为true时,生成的查询计划有两个MapReduce任务。

在第一个MapReduce中,map的输出结果集合会随机分布到reduce中,每个reduce做部分聚合操作,并输出结果。

这样处理的结果是,相同的Group By Key有可能分发到不同的reduce中,从而达到负载均衡的目的;

第二个MapReduce任务再根据预处理的数据结果按照Group By Key分布到reduce中(这个过程可以保证相同的Group By Key分布到同一个reduce中),最后完成最终的聚合操作。

但是这个处理方案对于我们来说是个黑盒,无法把控。

那么在日常需求的情况下如何处理这种数据倾斜的情况呢;

sample采样,获取哪些集中的key;
将集中的key按照一定规则添加随机数;
进行join,由于打散了,所以数据倾斜避免了;
在处理结果中对之前的添加的随机数进行切分,变成原始的数据。

 

七、小文件过多优化例子

7.1 有个表由于上游为kafka小时抽到hdfs,然后hive加分区。一天数据量233.6 G

导致有很多小文件6000+,每个文件大小不一致,6mb ~ 50m之间

7.2 方式1,设置合并为1G。产出后文件大小为1G左右,原文件个数6000+ 小文件合并后输出文件为245个

--1G一个文件
set hive.exec.reducers.bytes.per.reducer=1024000000;
select * from dw.temp_wang
where dt = '20220713'
--rand(1)防止stage失败导致数据增多或变少。均匀分发
distribute by cast(rand(1)*1000 as int)  

7.3 把set参数去除后尝试,看是否分发为1000个文件

--set hive.exec.reducers.bytes.per.reducer=1024000000;

只使用:distribute by cast(rand(1)*1000 as int)

实际创建980个文件,因为一天文件总量为233.6 G,设置为1000个文件达不到。

使用默认值256m*1000=256GB   大于  一天数据量233.6 G

7.4 把set参数去除后尝试,因为上面一步达不到1000个文件,调整为500个

--set hive.exec.reducers.bytes.per.reducer=1024000000;
只使用:distribute by cast(rand(1)*500 as int)

一天数据量233.6 G / 500 约为每个文件478MB,然而实际文件个数为980个文件。

可以看到创建了一些500mb左右的文件,还有些空文件。不了解原因

 

7.5 增加set参数,设置文件大小1G,目标500个文件。尝试下空文件是否还有

set hive.exec.reducers.bytes.per.reducer=1024000000;
distribute by cast(rand(1)*500 as int)

空文件没了,文件大小也变成1G左右,文件个数245,比设置的500个要小。 

下面准备考虑下文件大小设置为150mb,这样设置的500个文件预计75G放不下,看看处理效果如何

 

7.6改文件大小为150mb,500个文件,看看效果如何

set hive.exec.reducers.bytes.per.reducer=150000000;
distribute by cast(rand(1)*500 as int)

实际生成文件个数1009, 设置的两个参数未生效,存在500mb文件和空文件

 

7.7 改文件大小为2048mb,80个文件,看看效果如何

set hive.exec.reducers.bytes.per.reducer=2048000000;
distribute by cast(rand(1)*80 as int)

实际3.2g左右一个文件,存在一些空文件。总计123个文件,80个大文件,43个空文件。

设置的80不生效

八、Join优化

hive.auto.convert.join : 是否自动转换为mapjoin

hive.mapjoin.smalltable.filesize : 小表的最大文件大小,默认为25000000,即25M

hive.auto.convert.join.noconditionaltask : 是否将多个mapjoin合并为一个

hive.auto.convert.join.noconditionaltask.size : 多个mapjoin转换为1个时,所有小表的文件大小总和的最大值。

例:一个大表顺序关联3个小表a(10M), b(8M),c(12M),如果hive.auto.convert.join.noconditionaltask.size的值:
  小于18M,则无法合并mapjoin,必须执行3个mapjoin;
  大于18M小于30M,则可以合并a和b表的mapjoin,所以只需要执行2个mapjoin;
  大于30M,则可以将3个mapjoin都合并为1个。
合并mapjoin有啥好处呢?因为每个mapjoin都要执行一次map,需要读写一次数据,所以多个mapjoin就要做多次的数据读写,合并mapjoin后只用读写一次,自然能大大加快速度。但是执行map是内存大小是有限制的,在一次map里对多个小表做mapjoin就必须把多个小表都加入内存,为了防止内存溢出,所以加了hive.auto.convert.join.noconditionaltask.size参数来做限制。不过,这个值只是限制输入的表文件的大小,并不代表实际mapjoin时hashtable的大小。
我们可以通过explain查看执行计划,来看看mapjoin是否生效。
hive.mapjoin.localtask.max.memory.usage : 将小表转成hashtable的本地任务的最大内存使用率,默认0.9
hive.mapjoin.followby.gby.localtask.max.memory.usage : 如果mapjoin后面紧跟着一个group by任务,这种情况下 本地任务的最大内存使用率,默认是0.55
hive.mapjoin.check.memory.rows : localtask每处理完多少行,就执行内存检查。默认为100000
使用mapjoin时还要注意,用作join的关联字段的字段类型最好要一致。
我就碰到一个诡异的问题,执行mapjoin 的local task时一直卡住,40万行的小表处理了好几个小时,正常情况下应该几秒钟就完成了。查了好久原因,结果原来是做join的关联字段的类型不一致,一边是int, 一边是string,hive解释计划里显示它们都会被转成double再来join

八、查询数据优化

1、Fetch抓取

Hive中对某些情况的查询可以不必使用MapReduce计算,例如:select * from score 。这种情况下,Hive可以直接度去score对应的存储目录下的文件,然后输出查询结果到控制台,通过设置hive.fetch.task.conversion参数,可以控制查询语句是否走MapReduce。设置成more不会走mapreduce计算,直接拿文件输出

set hive.fetch.task.conversion=more; 

posted @ 2022-06-14 16:21  黑水滴  阅读(380)  评论(0编辑  收藏  举报