全面总结Hive性能优化(二)

上一篇已经从各方面总结了Hive的优化,按很多时候要解决数据倾斜才是优化的关键。

在MapReduce程序中,大量的相同key被partition分配到一个分区里,使这个节点承受着巨大的压力,而其他节点计算完毕后要一直等待这个忙碌的节点,这样一来也拖累了整体的计算时间,使数据的生产效率十分低下,总而言之这都是数据倾斜造成的。

造成数据倾斜的原因有很多,这里总结下以下几点原因:

  1. key分布不均匀
  2. 业务数据本身的特性
  3. 建表时考虑不周
  4. 某些SQL语句本身就有数据倾斜

数据倾斜一般可以分为三种:

  1. Mapper阶段数据倾斜
  2. Join阶段数据倾斜
  3. Reduce阶段数据倾斜

解决数据倾斜

1.Mapper阶段数据倾斜

可以修改读取数据的表的任务,比如对key进行随机化处理,最后插入数据时按照均衡的key值重新分布。也就是在最后加上distribute by…。
distribute by 的作用在于控制 map 中的输出在 reducer 中是如何进行划分的。使用distribute by 可以保证相同key的记录被划分到一个 Reduce 中。

如果Mapper的任务数比较少,比如200以内,可以考虑增加Mapper的任务数,从而减少单个任务的处理数据量和执行时间

2.Join阶段数据倾斜

常见的数据倾斜类型,可按照表的大小和join方式的不同对应有多种处理方式
上篇文章已经介绍过了,在这里就不过多叙述。

3.Reduce 阶段的数据倾斜

原因:

  1. Group by后面的字段值存在数据倾斜(但针对单个distinct比较有效,多个没效果)
  2. Distinct后面的字段加上group by后面的字段(如果有group by的话,没有就是distinct后面的字段)存在数据倾斜
  3. 动态分区导致数据倾斜
  4. 窗口函数中partition by后面的字段存在数据倾斜

解决group by 后的字段数据倾斜
set hive.groupby.skewindata = true 默认false

有数据倾斜的时候在group by之前增加一步distribute,把相同key的数据打散到多个instance上处理,从而达到负载均衡的目的。

解决同一个程序中有多个distinct(3个以上)
用打标+聚合的方式代替distinct的聚合

尽量减少程序中的distinct,其实去重运算是很慢的,尤其是多个count(distinct)的时候,你会发现为什么源表的数据量经过map后怎么变大了;
有多少个count(distinct)就会变大几倍,导致处理很慢。

利用窗口函数
可以减少1次join操作,从而提高计算性能

增加中间表减少任务数据处理量
把多个程序中类似的处理逻辑提炼到同一个任务中处理,生产中间表供后续任务共用,可以有效地减少后续任务的数据处理量

增加分区减少后续任务数据处理量
这个字段可枚举,数值不宜太多,多了表数据不能保留太久;使用这个字段作为条件能有效减少记录数。
如果这个字段记录数集中在某一个值上,且后续都是用这个值过滤,那就不太适合。

4.有数据倾斜时进行负载均衡

此处需要设定 hive.groupby.skewindata,当选项设定为 true 时,生成的查询计划有两 个 MapReduce 任务。

在第一个 MapReduce 中,map 的输出结果集合会随机分布到 reduce 中, 每个 reduce 做部分聚合操作,并输出结果。
这样处理的结果是,相同的 Group By Key 有可 能分发到不同的 reduce 中,从而达到负载均衡的目的。

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

5.无效ID在关联时的数据倾斜问题

问题:日志中常会出现信息丢失,比如每日约为 20 亿的全网日志,其中的 user_id 为主 键,在日志收集过程中会丢失,出现主键为 null 的情况;
如果取其中的 user_id 和 bmw_users 关联,就会碰到数据倾斜的问题。原因是 Hive 中,主键为 null 值的项会被当做相同的 Key 而分配进同一个计算 Map。

解决方法 1:
user_id 为空的不参与关联,子查询过滤 null

SELECT * FROM log a LEFT OUTER
JOIN bmw_users b ON
a.user_id IS NOT NULL AND a.user_id=b.user_id 
UNION All SELECT * FROM log a WHERE a.user_id IS NULL;

解决方法 2:
函数过滤 null

SELECT * FROM log a LEFT OUTER
JOIN bmw_users b ON
CASE WHEN a.user_id IS NULL THEN CONCAT(‘dp_hive’,RAND()) ELSE 
a.user_id END =b.user_id;

调优结果:
原先由于数据倾斜导致运行时长超过 1 小时,解决方法 1 运行每日平均时长 25 分钟,
解决方法 2 运行的每日平均时长在 20 分钟左右。优化效果很明显。

在工作中总结出:解决方法2比解决方法1效果更好,不但IO少了,而且作业数也少了。解决方法1中log读取两次,job 数为2。解决方法2中 job 数是1。

这个优化适合无效 id(比如-99、 ‘’,null 等)产生的倾斜问题。
把空值的 key 变成一个字符串加上随机数,就能把倾斜的 数据分到不同的Reduce上,从而解决数据倾斜问题。
因为空值不参与关联,即使分到不同 的 Reduce 上,也不会影响最终的结果。

6.不同数据类型关联产生的倾斜问题

同数据类型 id 的关联会产生数据倾斜问题。
一张表 s8 的日志,每个商品一条记录,要和商品表关联。但关联却碰到倾斜的问题。
s8 的日志中有 32 为字符串商品 id,也有数值商品 id,日志中类型是 string 的,但商品中的 数值 id 是 bigint 的。
猜想问题的原因是把 s8 的商品 id 转成数值 id 做 hash 来分配 Reduce, 所以字符串 id 的 s8 日志,都到一个 Reduce 上了,解决的方法验证了这个猜测。

解决方法:
把数据类型转换成字符串类型

SELECT * FROM s8_log a 
LEFT OUTERJOIN r_auction_auctions b
ON a.auction_id=CASE(b.auction_id AS STRING)

调优结果显示:
数据表处理由 1 小时 30 分钟经代码调整后可以在 20 分钟内完成。

7.计算资源优化解决数据倾斜

并发优化

set hive.exec.parallel=true;
set hive.exec.parallel.thread.number=8;

参数1:控制在同一个SQL中的不同的job是否可以同时运行(job之间没有前后依赖的都可以并行执行),默认为false
参数2:同一个SQL允许并行任务的最大线程数

数据出现倾斜时的优化

set hive.map.aggr=true;
set hive.groupby.skewindata=true;
set hive.groupby.mapaggr.checkinterval=100000;
set hive.optimize.skewjoin=true;
set hive.skewjoin.key=100000;

参数1:在mapper端部分聚合,相当于Combiner 。Map-Side聚合(一般在聚合函数sum,count时使用)
参数2:当选项设定为 true,生成的查询计划会有两个 MR Job。
第一个 MR Job 中,Map 的输出结果集合会随机分布到 Reduce 中,
每个 Reduce 做部分聚合操作,并输出结果。
这样处理的结果是相同的 Group By Key 有可能被分发到不同的 Reduce 中,从而达到负载均衡的目的;

第二个 MR Job 再根据预处理的数据结果按照 Group By Key 分布到 Reduce 中(这个过程可以保证相同的 Group By Key 被分布到同一个 Reduce 中);最后完成最终的聚合操作。
参数3:这是group的键对应的记录条数超过这个值则会进行分拆,值根据具体数据量设置。
参数5:join的键对应的记录条数超过这个值则会进行分拆,值根据具体数据量设置;
hive 在运行的时候没有办法判断哪个 key 会产生多大的倾斜,所以使用这个参数控制倾斜的阈值,如果超过这个值,新的值会发送给那些还没有达到的 reduce,对full join无效。

如果你不知道设置多少,可以就按官方默认的1个reduce 只处理1G的算法,那么 skew_key_threshold = 1G/平均行长,或者默认直接设成250000000 (差不多算平均行长4个字节)。

小文件合并

输入合并;即在Map前合并小文件。这个方法即可以解决之前小文件数太多,
导致mapper数太多的问题;还可以防止输出小文件合数太多的问题(因为mr只有map时,mapper数就是输出的文件个数)。

输出合并;即在输出结果的时候合并小文件。

输入文件合并

set hive.hadoop.supports.splittable.combineinputformat=true;
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
set mapred.max.split.size=2048000000;
set mapred.min.split.size.per.node=2048000000;
set mapred.min.split.size.per.rack=2048000000;

参数1:打开合并开关
参数2:执行Map前进行小文件合并
参数3:每个Map最大输入大小,默认2G
参数4:一个节点上split的至少的大小 ,决定了多个data node上的文件是否需要合并
参数5:一个交换机下split的至少的大小,决定了多个交换机上的文件是否需要合并

输出文件合并

set hive.merge.mapfiles=true;
set hive.merge.mapredfiles=true;
set hive.merge.size.per.task=25610001000;
set hive.merge.smallfiles.avgsize=16000000;

参数1:在Map-only的任务结束时合并小文件
参数2:在Map-Reduce的任务结束时合并小文件
参数3:合并后每个文件的大小,默认256000000
参数4:平均文件大小,参数值是决定是否执行合并操作的阈值,默认16000000触发合并的条件

内存优化

container的内存运行mapper的容器的物理内存
set mapreduce.map.memory.mb=2048;

jvm堆内存

set mapred.child.map.java.opts=’-Xmx2048M’;
set mapreduce.map.java.opts=’-Xmx2048M’;
set mapreduce.reduce.memory.mb=2048;
set mapred.child.reduce.java.opts=’-Xmx2048m’;
set mapreduce.reduce.java.opts=’-Xmx2048M’;

app内存

set yarn.app.mapreduce.am.resource.mb=3000;
set yarn.app.mapreduce.am.command-opts=’-Xmx2048m’;
am指Yarn中AppMaster,针对MapReduce计算框架就是MR AppMaster,
通过配置这两个选项,可以设定MR AppMaster使用的内存。

一般看hadoop日志时可以看到map/reduce,但是当没有map/reduce时就开始报beyond memory limit类似的错时,说明是am的内存不够.

在yarn container这种模式下,map/reduce task是运行在Container之中的,所以上面提到的mapreduce.map(reduce).memory.mb大小都大于mapreduce.map(reduce).java.opts值的大小。
mapreduce.{map|reduce}.java.opts能够通过Xmx设置JVM最大的heap的使用,
一般设置为0.75倍的memory.mb,因为需要为java code等预留些空间。

Hive压缩

map输出压缩

set hive.exec.compress.intermediate=true;
set mapred.map.output.compression.codec=org.apache.hadoop.io.compress.
SnappyCodec;

结果输出压缩

set hive.exec.compress.output=true;
set mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec;
set mapred.output.compression.type=BLOCK;

猜你喜欢

转载自blog.csdn.net/zp17834994071/article/details/107449958