文章目录
倾斜现象
任务进度长时间卡在99% 有的时候甚至100%,查看任务监控页面.发现只有少量的reduce 子任务未完成,因为其处理的数据量和其他reduce 差异过大,单一reduce 记录数与平均记录数差异过大,最长时长远大于平均时长
产生场景
大部分发生在reduce阶段,较少发生在map阶段,map端的数据倾斜主要是hdfs存储块分布不均衡,reduce阶段数据倾斜几乎都是没有考虑到某种key值数据量较多场景
Reduce阶段最容易出现数据倾斜的两个场景分别是Join和Count
join阶段
- 其中一个表较小,但是key集中,后果是分发到某一个或几个reduce上的数据远高于平均值
- 大表与大表,但是分桶的判断字段0值或空值过多,空值都由一个reduce处理,非常慢
- group by 维度过小,某值的数量过多,处理某值的reduce耗时
- Count Distinct 某特殊值过多 造成处理此特殊值的reduce耗时
产生原因
- key分布不均匀
- 业务数据本身特性
- 建表时考虑不周
- 某些SQL语句本身就会数据倾斜
解决方案
开启负载均衡
-- map端的Combiner,默认为ture
set hive.map.aggr=true;
-- 开启负载均衡
set hive.groupby.skewindata=true (默认为false)
mapreduce进程会产生两个额外的MR job
第一步: MR Job 中Map输出结果集合首先会随机分配到Reduce 中,然后每个reduce 做局部聚合操作并输出结果,相同的group by key 可能分到不同reduce job中,达到负载均衡目的
第二步: MR job 在根据预处理数据结果,按照Group by Key 分布到reduce 中
(保证相同的group by key 被分布到同一个reduce中),最后完成聚合操作;
小表join大表,某个key过大
通常做法是将倾斜的数据存到分布式缓存中,分发到各个Map任务所在节点。在Map阶段完成join操作,即MapJoin,这避免了 Shuffle,从而避免了数据倾斜。
默认是自动开启
-- 自动开启MAPJOIN优化,默认值为true,
set hive.auto.convert.join=true;
-- 通过配置该属性来确定使用该优化的表的大小,如果表的大小小于此值就会被加载进内存中,默认值为2500000(25M),
set hive.mapjoin.smalltable.filesize=2500000;
使用默认启动该优化的方式如果出现莫名其妙的BUG(比如MAPJOIN并不起作用),就将以下两个属性置为fase手动使用MAPJOIN标记来启动该优化:
-- 关闭自动MAPJOIN转换操作
set hive.auto.convert.join=false;
-- 不忽略MAPJOIN标记
set hive.ignore.mapjoin.hint=false;
select /* + mapjoin(id) */ from xxx
将表放到Map端内存时,如果节点的内存很大,但还是出现内存溢出的情况,我们可以通过这
set mapreduce.map.memory.mb= xxx
表中作为关联条件的字段值为0或空值的较多
解决方式:给空值添加随机key值,将其分发到不同的reduce中处理。由于null值关联不上,所以对结果无影响。
原因: 数据放在同一个reduce不是因为字段join 能不能join上,而是shuffle 阶段的hash操作,只要key的hash结果都是一样,它们就会被拉到同一个reduce中
-- 方案一、给空值添加随机key值,将其分发到不同的reduce中处理。由于null值关联不上,所以对结果无影响。
SELECT *
FROM log a left join users b
on case when a.user_id is null then concat('hive',rand()) else a.user_id end = b.user_id;
-- 方案二:去重空值
SELECT a.*,
FROM
(SELECT * FROM users WHERE LENGTH(user_id) > 1 OR user_id IS NOT NULL ) a
JOIN
(SELECT * FROM log WHERE LENGTH(user_id) > 1 OR user_id IS NOT NULL) B
ON a.user_id; = b.user_id;
表中作为关联条件的字段重复值过多
SELECT a.*,
FROM
(SELECT * FROM users WHERE LENGTH(user_id) > 1 OR user_id IS NOT NULL ) a
JOIN
(SELECT * FROM (SELECT *,row_number() over(partition by user_id order by create_time desc) rk FROM log WHERE LENGTH(user_id) > 1 OR user_id IS NOT NULL) temp where rk = 1) B
ON a.user_id; = b.user_id;
表不同数据类型关联产生数据倾斜
由于hive是弱类型,因此join关联时.a表字段为int, b表字段为String,里面的数值原型是int 和string ,对于int类型的数据,会hash分配,但是string 类型会被分配同一个hash id ,汇聚成一个reduce,引发数据倾
斜
-- 如果key字段既有string类型也有int类型,默认的hash就都会按int类型来分配,那我们直接把int类型都转为string就好了,这样key字段都为string,hash时就按照string类型分配了:
方案一:把数字类型转换成字符串类型
SELECT * FROM users a LEFT JOIN logs b ON a.usr_id = CAST(b.user_id AS string);
方案二:建表时按照规范建设,统一词根,同一词根数据类型一致
count distinct 大量相同特殊值
由于SQL 中distinct 操作本身会有一个全局排序过程,一般情况下,不建议count distinct 方式进行去重操作,除非表的数据量较少,当SQL中不存在分组字段,count distinct 操作只有一个reduce任务,该任务会对全部数据进行去重操作
-- 可能会造成数据倾斜的sql
select a,count(distinct b) from t group by a;
-- 先去重、然后分组统计
select a,sum(1) from (select a, b from t group by a,b) group by a;
-- 总结: 如果分组统计的数据存在多个distinct结果,可以先将值为空的数据占位处理,分sql统计数据,然后将两组结果union all进行汇总结算。
数据量过大
对于多维度聚合计算时.分组聚合字段过多,数据量较大,map端聚合不能很好的数据压缩情况下,导致map端产生数据较大,导致OOM
-- 造成倾斜或内存溢出的情况
-- sql01
select a,b,c,count(1)from log group by a,b,c with rollup;
-- sql02
select a,b,c,count(1)from log grouping sets a,b,c;
-- 解决方案
-- 可以拆分上面的sql,将with rollup拆分成如下几个sql
select a,b,c,sum(1) from (
SELECT a, b, c, COUNT(1) FROM log GROUP BY a, b, c
union all
SELECT a, b, NULL, COUNT(1) FROM log GROUP BY a, b
union all
SELECT a, NULL, NULL, COUNT(1) FROM log GROUP BY a
union all
SELECT NULL, NULL, NULL, COUNT(1) FROM log
) temp;
-- 结论:但是上面这种方式不太好,因为现在是对3个字段进行分组聚合,那如果是5个或者10个字段呢,那么需要拆解的SQL语句会更多。
-- 在Hive中可以通过参数 hive.new.job.grouping.set.cardinality 配置的方式自动控制作业的拆解,该参数默认值是30。
-- 该参数主要针对grouping sets/rollups/cubes这类多维聚合的操作生效,如果最后拆解的键组合大于该值,会启用新的任务去处理大于该值之外的组合。如果在处理数据时,某个分组聚合的列有较大的倾斜,可以适当调小该值。
set hive.new.job.grouping.set.cardinality=10;
select a,b,c,count(1)from log group by a,b,c with rollup;
















