1、什么是数据倾斜?
数据分布不均匀,造成数据大量的集中到一点,造成数据热点
2、Hadoop 框架的特性
A、不怕数据大,怕数据倾斜
B、Jobs 数比较多的作业运行效率相对比较低,如子查询比较多
C、 sum,count,max,min 等聚集函数,通常不会有数据倾斜问题
3、主要表现
任务进度长时间维持在 99%或者 100%的附近,查看任务监控页面,发现只有少量 reduce 子任务未完成,因为其处理的数据量和其他的 reduce 差异过大。 单一 reduce 处理的记录数和平均记录数相差太大,通常达到好几倍之多,最长时间远大 于平均时长。
4、容易数据倾斜情况
A、group by 不和聚集函数搭配使用的时候
B、count(distinct),在数据量大的情况下,容易数据倾斜,因为 count(distinct)是按 group by 字段分组,按 distinct 字段排序
C、 小表关联超大表 join
一、Map倾斜
根本原因:读入的文件块的数据分布不均匀。
1、上游表文件的大小不均匀,并且小文件特别多,导致当前表Map端读取的数据分布不均匀,引起长尾;
- 处理方式
对上游合并小文件,同时调节本节点的小文件的参数
# 输入合并
//CombineFileInputFormat作用:将多个小文件打包成一个InputSplit提供给一个Map处理
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
set hive.hadoop.supports.splittable.combineinputformat = true;
//执行Map前进行小文件合并
set mapred.max.split.size=256000000;
//每个Map最大输入大小
set mapred.min.split.size.per.node=100000000;
//一个节点上split的至少的大小
set mapred.min.split.size.per.rack=100000000;
//一个交换机下split的至少的大小
# 输出合并
set hive.merge.mapfiles = true
//在Map-only的任务结束时合并小文件
set hive.merge.tezfiles=true;
set hive.merge.mapredfiles = true
//在Map-Reduce的任务结束时合并小文件
set hive.merge.size.per.task = 256*1000*1000
//合并文件的大小
set hive.merge.smallfiles.avgsize=16000000
//当输出文件的平均大小小于该值时,启动一个独立的map-reduce任务进行文件merge
2、Map端做聚合时,由于某些Map Instance读取文件的某个值特别多而引起长尾,主要是指Count Distinct操作。
- 处理方式
distribute by :用来控制map输出结果的分发,即map端如何拆分数据给reduce端。
通过distribute by rand() 将Map端分发后的数据重新按照随机值再进行一次分发。使用后,Map端只负责数据的分发,不再有复杂的聚合或者笛卡尔积操作,因此不会导致Map端拖尾。
二、Join倾斜
1、Join的某路输入比较小,可以采用MapJoin,避免分发引起长尾
map join 概念:将其中做连接的小表(全量数据)分发到所有 MapTask 端进行 Join,从 而避免了 reduceTask,前提要求是内存足以装下该全量数据。
以大表 a 和小表 b 为例,所有的 maptask 节点都装载小表 b 的所有数据,然后大表 a 的 一个数据块数据比如说是 a1 去跟 b 全量数据做连接,就省去了 reduce 做汇总的过程。 所以相对来说,在内存允许的条件下使用 map join 比直接使用 MapReduce 效率还高些, 当然这只限于做 join 查询的时候。
- MapJoin 具体用法:
select /* +mapjoin(a) */ a.id aid, name, age
from a join b on a.id = b.id;
select /* +mapjoin(movies) */ a.title, b.rating
from movies a join ratings b
on a.movieid =b.movieid;
- 在 hive0.11 版本以后会自动开启 map join 优化,由两个参数控制:
set hive.auto.convert.join=true;
//设置 MapJoin 优化自动开启
set hive.mapjoin.smalltable.filesize=25000000
//设置小表不超过多大时开启 mapjoin 优化
2、Join的每路输入都很大,且长尾是空值导致的。
场景说明:
在日志中,常会有信息丢失的问题,比如日志中的 user_id,如果取其中的 user_id 和用户表中的 user_id 相关联,就会碰到数据倾斜的问题。
- 解决方案 1:user_id 为空的不参与关联
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 c where c.user_id is null;
- 解决方案 2:赋予空值新的 key 值
select * from log a left outer join user b on
case when a.user_id is null then concat('hive',rand())
else a.user_id
end = b.user_id
总结
方法 2 比方法 1 效率更好,不但 IO 少了,而且作业数也少了,方案 1 中,log 表 读了两次,jobs 肯定是 2,而方案 2 是 1。这个优化适合无效 id(比如-99,’’,null)产 生的数据倾斜,把空值的 key 变成一个字符串加上一个随机数,就能把造成数据倾斜的 数据分到不同的 reduce 上解决数据倾斜的问题。
改变之处:使本身为 null 的所有记录不会拥挤在同一个 reduceTask 了,会由于有替代的 随机字符串值,而分散到了多个 reduceTask 中了,由于 null 值关联不上,处理后并不影响最终结果。
3、Join的每路输入都很大,且长尾是热点值导致的,可以对热点值和非热点值分别进行处理,再合并数据。
具体地,先将热点key取出,对于主表数据用热点key切分成热点数据和非热点数据两部分处理,最后合并。
三、Reduce倾斜
主要原因:key的数据分布不均匀
1、对同一个表按照维度对不同的列进行Count distinct操作,造成Map端数据膨胀,从而使得下游的Join和Reduce出现链路上的长尾。
2、Map端直接做聚合时出现key值分布不均匀,造成Reduce端长尾。
对热点key单独处理,再通过union all合并
3、动态分区数过多时可能造成小文件过多,造成Reduce端长尾。
把符合不同条件的数据放到不同的分区,避免多次通过Insert Overwrite写入表中。但是动态分区也有可能会带来小文件过多的困扰
4、多个Distinct同时出现在一段SQL代码中,数据会被分发多次,不仅会造成数据膨胀N倍,还会把长尾现象放大N倍
总结:目前reduce端的数据倾斜很多是由count distinct引起的。
【参考】
Hive的数据倾斜