Hive 执行 MapReduce 过程中经常会出现数据倾斜问题,具体表现为:作业经常在 Reduce 过程完成 99% 的时候一直停留,最后 1% 一直保持很久才完成。这种情况是由于:在数据量很大的情况下,在 MapReduce 的 Shuffle 过程执行后,key值分布到 Reducer 节点不均匀;有的 key 少,哈希后被分在不同节点中没有问题,但是有的 key 特别的多,过于集中了,全被分配在一个 Reducer 节点,所以其他的 Reducer 都执行完了都在等这个量大的 key 值,这就导致了数据倾斜。通俗的话来讲就是,一堆干完活的人等那个干的最慢的人,不是因为那个人能力差,大家能力水平都相同,是他真的干不完……被分配太多了,别人做完了也没法帮忙。
所以这也违背了 MapReduce 方法论产生的核心思路,不怕活儿多,活多咱们可以多分配人手;最怕的就是活分配不均匀,有人干的多,有人干的少,出现时间上的浪费。这些工作在 MapReduce 过程中,往往都出现在 GROUP
BY 等分组,各种类型 JOIN 过程中。常见产生数据倾斜的原因大致有以下几种:大量空值
某个 key 值大量重复
不同数据类型关联
COUNT(DISTINCT)
接下来详细介绍各种出现以及如何避免这类问题的出现。
二、问题分类及解决思路
1.大量空值产生数据倾斜
解决思路:利用随机数将空值进行随机填充,注意:不要让随机数碰撞到其他值,提前要测试下是否有膨胀现象发生,之所以选择以下例子是因为流量表中往往存在无”主键”的情况,c端用户不登录,就不会在流量表记录用户唯一值。例如:
优化前:1select * from click_log a left join users b on a.user_id = b.user_id;
优化方法1. 让 user_id 为空的不参与关联,1
2
3select * from click_log a join users b on a.user_id is not null and a.user_id = b.user_id
union all
select * from click_log a where a.user_id is null;
优化方法2. 让随机数冲散堆积在一个人 Reduce 中的很多 null 值,1
2
3select * from click_log a left join users b
on case when a.user_id is null then concat('dp_hive',rand())
else a.user_id end = b.user_id;
解释:如果key均为空值,大量的key会分布在同一个Reduce节点上;在其他Reduce节点完成ReduceTask后,存在大量空值的Reduce还未完成Task,因此产生数据倾斜。concat('dp_hive',rand())是为了把空值变成一个字符串加上随机数的,把null值倾斜的数据分布在不同Reduce节点上,间接把倾斜的数据分布在不同Reduce上。
2.某 key 值大量重复产生数据倾斜
其实,当key值非空,但某个key出现大量重复的情况的解决方案和上述空值情况相同,均为引入随机数进行优化。
优化前:1
2select a.key as key, count(b.pv) as pv from test_table1 a inner join test_table2 b
on a.key = b.key group by 1;
优化后:1
2
3
4
5
6
7
8
9
10select a.key as key, b.pv as pv
from
(select key from test_table1) a
inner join
(select key, sum(pv) as pv
from
(select key, round(rand()*1000) as rnd, count(1) as pv from test_table2 group by 1,2) tmp
group by 1;
) b
on a.key = b.key;
解释: round(rand()*1000) as rnd –> sum(pv) 加入随机,将本来ReduceTask在一组的key,拆分成多组进行处理,增加并发度。
3.不同数据类型关联产生数据倾斜
用户表user_id字段为 bigint,click_log 表中user_id字段既有string类型也有bigint类型。当按照user_id进行两个表的join操作时,默认的hash操作会按bigint型的id来进行分配,这样会导致所有string类型id的记录都分配到一个Reduce中,例如:
优化前:1
2select * from click_log a left join users b
on a.user_id = b.user_id;
优化后:1
2select * from users a left join click_log b
on a.user_id = cast(b.user_id as string)
4.COUNT(DISTINCT) 产生数据倾斜
首先,说说 group by 和 distinct 的区别。我在工作过程中实践过很多场景,发现在处理时间上并没有什么本质区别,都是在一个 job 中,出现一个 reduce 过程。那么为什么一定要要强调用 group by 代替 distinct 呢? 我在网上搜了很多资料,都没有说清楚这个问题的本质。接下来我来说说我的理解:
其实,大部分情况我们根本不用代替,因为 大部分的情况 数据是不倾斜的。
举个栗子,我们经常计算每个用户这一年变换手机号次数,Email次数等等,当然不会倾斜,因为谁没事儿老去更换手机号呢!
所以,数据不倾斜的时候基本上两个过程是等价的。但是,一旦出现数据倾斜,还是要代替一下,因为 group by 覆盖到了很多 hive 计算引擎参数设置,例如:set hive.groupby.mapaggr.checkinterval = 100000; – 这个是 grby 的键对应的记录条数超过这个值则会进行优化
set hive.groupby.skewindata = true; – 如果 grby 过程中出现倾斜,设置成 true,会自动将相同的reduceKey进行 Hashing
除此之外,join 也涉及到了一些,如下:set hive.skewjoin.key = 100000; – 这是 join 的键对应的记录条数超过这个值则会进行优化;
set hive.optimize.skewjoin = true; – 如果是 join 过程中出现数据倾斜,设置成 true
其次,count(distinct) 会压力都积压在一个 reduce 过程中,导致一个job处理太多数据,导致数据倾斜,改成 count(1) + group by 的处理方式,这样会将整个过程解耦,进而分解整体过程中的压力。
最后,有一点自己在写 Hive Sql 时候的体会,代码可读性和性能优化,一定都要考虑,过于复杂的代码逻辑一定要加注释,增强代码可读性,举个栗子:1select a, sum(b), count(distinct c), ... from T group by a;
优化后本应该是:1
2
3
4
5
6
7
8select a, sum(b) as b,count(c) as c, ... from
(
select a, b, null as c, ... from T group by a,b
union all
select a, 0 as b, c, ... from T group by a,c
union all
...
) tmp1 group by a;
其实,企业级的 hive 在处理 T+1 数据时,处理的速度并不会相差太远,即优化程度不高的情况下,建议保留业务逻辑。