文章目录
- 1. 数据倾斜是什么
- 2. 数据倾斜的表现
- 2.1 Hive中的数据倾斜
- 2.2 spark中的数据倾斜
- 3. 数据倾斜的原因
- 3.1 Shuffle
- 3.2 数据本身
- 3.3 业务逻辑
- 4. 数据倾斜的解决方案
- 5.举个栗子
- 5.1 由空值造成的数据倾斜
- 5.2 count(distinct)的倾斜问题
- 5.3 不同数据类型关联产生数据倾斜
- 5.4 小表不小不大,怎么用 map join 解决倾斜问题
- 6.总结
1. 数据倾斜是什么
数据倾斜就是我们在计算数据的时候,数据的分散度不够,导致大量的数据集中到了集群中的一台或者几台机器上计算,而集群中的其他节点空闲。这些倾斜了的数据的计算速度远远低于平均计算速度,导致整个计算过程过慢。
2. 数据倾斜的表现
2.1 Hive中的数据倾斜
1)虽说Hive最后也是用MR来执行,但是毕竟写的内容逻辑区别很大,一个是程序,一个是 sql,因此这里稍作区分。
2)表现Hadoop中的数据倾斜主要表现在、Reduce 阶段卡在 99.99%,一直不能结束。这里如果详细的看日志或者和监控界面的话会发现:
- 有一个多几个Reduce卡住
- 各种container报错
- OOM
- 异常的Reducer读写的数据量极大,至少远远超过其它正常的Reducer伴随着数据倾斜,最长时长远大于平均时长。甚至会出现任务被kill等各种诡异的表现。
3)Hive的数据倾斜,一般都发生在Sql中group by、join和count distinct 上,而且和数据逻辑绑定比较深。
函数 | 情形 | 后果 |
Join | 其中一个表较小,但是key集中 | 分发到某一个或几个Reduce上的数据远高于平均值 |
Join | 大表与大表,但是分桶的判断字段0值或空值过多 | 这些空值都由一个reduce处理,灰常慢 |
group by | group by 维度过小,某值的数量过多 | 处理某值的reduce灰常耗时 |
Count Distinct | 某特殊值过多 | 处理此特殊值的reduce耗时 |
2.2 spark中的数据倾斜
Spark 中的数据倾斜也很常见,这里包括Spark Streaming 和Spark Sql,表现主要有下面几种:
- Executor lost,OOM,Shuffle 过程出错;
- Driver OOM;
- 单个Executor执行时间特别久,整体任务卡在某个阶段不能结束;
- 正常运行的任务突然失败;
需要注意的是,在Spark streaming 程序中,数据倾斜更容易出现,特别是在程序中包含一些类似sql的join、group这种操作的时候。 因为Spark Streaming程序在运行的时候,一般不会分配特别多的内存,因此一旦在这个过程中出现一些数据倾斜,就十分容易造成OOM。
3. 数据倾斜的原因
一般来说造成数据倾斜的主要原因可以总结为以下几点:
- key分布不均匀
- 业务数据本身的特性
- 建表时考虑不周
- 某些SQL语句本身就有数据倾斜
3.1 Shuffle
Hadoop 和 Spark 在 Shuffle 过程中产生数据倾斜的原理基本类似即数据不均匀。因为数据分布不均匀,导致大量数据分配到一个节点。
3.2 数据本身
举一个栗子,假设我们有两张表:
users(用户信息表):userid,register_ip
ip(IP 表):ip,register_user_cnt
这可能是两个不同的人开发的数据表。如果我们的数据规范不太完善的话,会出现一种情况:user表中的register_ip 字段,如果获取不到这个信息,我们默认为null;但是在ip表中,我们在统计这个值的时候,为了方便,我们把获取不到 ip 的用户,统一认为他们的 ip 为 0。 两边其实都没有错的,但是一旦我们做关联了,这个任务会在做关联的阶段,也就是 sql 的 join on 的阶段卡死。
3.3 业务逻辑
数据往往和业务是强相关的,业务的场景直接影响到了数据的分布。 再举一个栗子,比如就说订单场景吧,在某一天,北京和上海两个城市做了强力推广,结果这两个城市的订单量增长了10000倍,其余城市的数据量不变。 然后我们要统计不同城市的订单情况,这样,一做 group by 操作,可能直接就数据倾斜了。
4. 数据倾斜的解决方案
很多数据倾斜的问题,基本上都是由于数据设计与业务的理解不足而造成的。通常情况下,通过更适合的数据预处理,异常值的过滤等,就可以解决数据倾斜的大部分问题。
解决数据倾斜的基本思路大致分为为三种:
- 业务逻辑
从业务逻辑的层面上来优化数据倾斜,比如上面的两个城市做推广活动导致那两个城市数据量激增的栗子,我们可以单独对这两个城市来做count,单独做时可用两次MR,第一次打散计算,第二次再最终聚合计算。完成后和其它城市做整合。 - 代码层面
- join
关于驱动表的选取,选用join key分布最均匀的表作为驱动表。
做好列裁剪和filter操作,以达到两表做join的时候,数据量相对变小的效果。 - 大小表Join
使用map join让小的维度表(1000条以下的记录条数) 先进内存。在map端完成join. - 大表Join大表
把空值的key变成一个字符串加上随机数,把倾斜的数据分到不同的reduce上,由于null值关联不上,处理后并不影响最终结果。 - count distinct大量相同特殊值
count distinct时,将值为空的情况单独处理,如果是计算count distinct,可以不用处理,直接过滤,在最后结果中加1。如果还有其他计算,需要进行group by,可以先将值为空的记录单独处理,再和其他计算结果进行union。 - group by维度过小
采用sum() group by的方式来替换count(distinct)完成计算。 - 特殊情况特殊处理
在业务逻辑优化效果的不大情况下,有些时候是可以将倾斜的数据单独拿出来处理。最后union回去。 - 自定义分区器
根据数据分布情况,自定义散列函数,将 key 均匀分配到不同 Reducer
- 配置调优
Hadoop 和 Spark都自带了很多的参数和机制来调节数据倾斜,合理利用它们就能解决大部分问题。
举个栗子:
- 设置该任务的每个 job 的 reducer 个数为 3 个。Hive 默认-1,自动推断。 set mapred.reduce.tasks=3
- hive.map.aggr=true
Map 端部分聚合,相当于Combiner - hive.groupby.skewindata=true
有数据倾斜的时候进行负载均衡,当选项设定为 true,生成的查询计划会有两个 MR Job。第一个 MR Job 中,Map 的输出结果集合会随机分布到 Reduce 中,每个 Reduce 做部分聚合操作,并输出结果,这样处理的结果是相同的 Group By Key 有可能被分发到不同的 Reduce 中,从而达到负载均衡的目的;第二个 MR Job 再根据预处理的数据结果按照 Group By Key 分布到 Reduce 中(这个过程可以保证相同的 Group By Key 被分布到同一个 Reduce 中),最后完成最终的聚合操作。
5.举个栗子
5.1 由空值造成的数据倾斜
拿之前ip为null的栗子说明,并通过设计的角度尝试解决它。
1. 有损的方法:找到异常数据,比如 ip 为 0 的数据,过滤掉。
2. 无损的方法:对分布不均匀的数据,单独计算,先对 key 做一层hash、加盐等打散操作,先将数据随机打散让它的并行度变大,再汇集。
解决方法:
- 对空值行筛选出来,不进行操作
select * from ip a
join users b
on a.ip is not null
and a.ip = b.register_ip
union all
select * from ip a
where a.ip is null;
- 将空值插入随机值后操作
select *
from ip a
left outer join users b
on case when a.ip is null then concat(‘hive’,rand() ) else a.ip end = b.register_ip;
方法2比方法1效率更好,不但io少了,而且作业数也少了。解决方法1中 log读取两次,jobs是2。解决方法2 job数是1 。这个优化适合无效 id (比如 -99 , ’’, null 等) 产生的倾斜问题。把空值的 key 变成一个字符串加上随机数,就能把倾斜的数据分到不同的reduce上 ,解决数据倾斜问题。
5.2 count(distinct)的倾斜问题
在 Hive 中,经常遇到 count(distinct)操作,这样会导致最终只有一个 Reduce 任务。 我们可以先 group by,再在外面包一层 count,就可以了。举个栗子,计算按用户名去重后的总用户量:
// 优化前只有一个reduce,先去重再count负担比较大
select name,count(distinct name)from user;
// 优化后
// 启动两个job,一个负责子查询(可以有多个reduce),另一个负责count(1)
select count(1) from (select name from user group by name) tmp;
5.3 不同数据类型关联产生数据倾斜
场景:用户表中user_id字段为int,log表中user_id字段既有string类型也有int类型。当按照user_id进行两个表的Join操作时,默认的Hash操作会按int型的id来进行分配,这样会导致所有string类型id的记录都分配到一个Reducer中。
解决方法:把数字类型转换成字符串类型
select * from users a
left outer join logs b
on a.usr_id = cast(b.user_id as string)
5.4 小表不小不大,怎么用 map join 解决倾斜问题
使用 map join 解决小表(记录数少)关联大表的数据倾斜问题,这个方法使用的频率非常高,但如果小表很大,大到map join会出现bug或异常,这时就需要特别的处理。 以下例子:
select * from log a
left outer join users b
on a.user_id = b.user_id;
users 表有 600w+ 的记录,把 users 分发到所有的 map 上也是个不小的开销,而且 map join 不支持这么大的小表。如果用普通的 join,又会碰到数据倾斜的问题。
在Hive0.11后,Hive默认启动该优化。只需调整以下参数即可自动判断并使用。
- hive.auto.convert.join
默认值为true,自动开户MAPJOIN优化。 - hive.mapjoin.smalltable.filesize
默认值为2500000(25M),通过配置该属性来确定使用该优化的表的大小,如果表的大小小于此值就会被加载进内存中。
6.总结
- 对于join,在判断小表不大于1G的情况下,使用map join。
- 对于group by或distinct,设定 hive.groupby.skewindata=true。决定 group by 操作是否支持倾斜数据。
注意:只能对单个字段聚合。
- 尽量使用上述的SQL语句调节进行优化。