数据倾斜的表现
任务进度长时间维持在99%(或100%),查看任务监控页面,发现只有少量(1个或几个)reduce子任务未完成。因为其处理的数据量和其他reduce差异过大导致key值分布不均。
单一reduce的记录数与平均记录数差异过大,通常可能达到3倍甚至更多。最长时长远大于平均时长。
数据倾斜的原因
- key分布不均匀
- 业务数据本身的特性
- 建表时考虑不周
- 某些SQL语句本身就有数据倾斜
进行哪些操作时可能会出现数据倾斜
- group by
- distinct
- join
关键词 | 情形 | 后果 | 解决 |
join | 小表关联大表 | 大表进内存的话可能会因为过大的原因,导致mapjoin无法实现,从而转为reduce join | 使用mapjoin,将小表加载到内存中 |
大表与大表,但是分桶的判断字段0值或空值过多 | 这些0值或空值都由一个reduce处理,极其慢 | 特殊值不参与关联单独处理后union all关联部分, 或使用随机函数rand赋予其他值 | |
group by | group by 维度过小, | 处理数量过多的key时reduce很耗时,从而导致整体很慢 | set hive.map.aggr=true;--map端聚合 set hive.groupby.skewindata=true;--负载均衡 |
某key的数量过多 | |||
count distinct | key值分布不均 | 处理key值量大的特殊值时reduce耗时 | 使用sum...group by代替。 |
典型业务场景
-------------------------------------------------------
话外补充:join时如何优化
关于驱动表的选取,选用join key分布最均匀的表作为驱动表
做好列裁剪和filter操作然后再进行join操作,以达到两表做join的时候,数据量相对变小的效果。
视情况选择mapjoin
-------------------------------------------------------
大小表Join:
使用map join让小的维度表(小于1G) 先进内存。在map端完成reduce
select y.id,y.b from A x join B y on x.id=y.id
该语句中B表有50亿行记录,A表只有几百行记录,而且B表中数据倾斜特别严重,某key值有10亿行,在运行过程中特别的慢,而且在reduece的过程中遇有内存不够而报错。
解决方法:select /*+ mapjoin(A)*/ x.id,y.b from A x join B y on x.id=y.id
非等值连接:
这种操作如果直接使用join的话语法不支持不等于操作,hive语法解析会直接抛出错误
如果把不等于写到where里会造成笛卡尔积,数据异常增大,速度会很慢,甚至会报错。
根据mapjoin的计算原理,MAPJION会把小表全部读入内存中,在map阶段直接拿另外一个表的数据和内存中表数据做匹配。这种情况下即使笛卡尔积也不会对任务运行速度造成太大的效率影响。
而且hive的where条件本身就是在map阶段进行的操作,所以在where里写入不等值比对的话,也不会造成额外负担。
select /*+ MAPJOIN(a) */
a.start_date, b.*
from dim_level a
join b
where b.allocate_date>=a.start_date
and b.allocate_date<end_date;
大表Join大表,某key值null
比如日志中常会有访客状态下user_id空的情况,如果取其中的user_id和用户表中的user_id 关联,会碰到数据倾斜的问题
解决方法1:null值不参与关联
select * from log a
join user 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 join user b
on case when a.user_id is null then rand() else a.user_id end = b.user_id;
对比以上两种方法,方法2更高效,相较方法1不仅io减少,作业数也少了。
大表join不大不小的表
select * from log a
left join user b
on a.user_id = b.user_id;
users表有几百万记录行,把users分发到所有的map上也是个不小的开销,而且map join不支持这么大的小表。如果用普通的join,又会碰到数据倾斜的问题。
select /*+mapjoin(x)*/ *
from log a
left join
(
select /*+mapjoin(c)*/d.*
from ( select distinct user_id from log ) c
join user d
on c.user_id = d.user_id
) x
on a.user_id = b.user_id;
count distinct场景
select count(distinct user_id)
from log a
where dt>='20200101' and dt<='20201015'
由于引入了DISTINCT,因此在Map阶段无法利用combine对输出结果消重,必须将id作为Key输出,在Reduce阶段再对来自于不同Map Task、相同Key的结果进行消重,计入最终统计值。
因为只有一个reducer在进行COUNT(DISTINCT uuid)的计算,所有的数据都流向唯一的一个reducer,导致数据倾斜
解决方法:
select sum(1)
from
(
select user_id
from log a
where dt>='20200101' and dt<='20201015'
group by user_id
) t
group by 场景
存在某key值量过大情况,可以分步,先采用随机数先中间聚合,再次聚合
补充:在group by 或count distinct场景下的数据倾斜可以设置参数协助优化
set hive.map.aggr=true;--map端聚合
set hive.groupby.skewindata=true;--负载均衡