数据倾斜
什么是数据倾斜
- Hadoop能够进行对海量数据进行批处理的核心,在于它的分布式思想,也就是多台服务器(节点)组成集群,进行分布式的数据处理。
- 举例:如果有10亿数据,一台电脑可能要10小时,现在集群有10台,可能1小时就够了,但是有可能大量的数据集中到一台或几台上,要5小时,发生了数据倾斜
数据倾斜的表现
- Mapreduce任务
- reduce阶段 卡在99.99%不动
- 各种container报错OOM(内存溢出)
- 读写数据量很大,超过其他正常reduce
- spark任务
- 个别task执行很慢
- 单个执行特别久
- shuffle出错
- sparkstreaming做实时算法使,会有executor出现内存溢出,但是其他的使用率很低
发生数据倾斜的原因
- shuffle是按照key,来进行values的数据的输出、拉取和聚合的,一旦发生shuffle,所有相同key的值就会拉到一个或几个节点上,个别key对应的数据比较多,就容易发生单个节点处理数据量爆增的情况。
- key分布不均匀
- 存在大量相同值的数据
- 存在大量异常值或者空值
- 业务数据本身的特性
- 例如某个分公司或某个城市订单量大幅提升几十倍甚至几百倍,对该城市的订单统计聚合时,容易发生数据倾斜。
- 某些SQL语句本身就有数据倾斜
- 两个表中关联字段存在大量空值(解决方法:去除或者加随机数),或是关联字段的数据不统一(解决方法:把数字类型转为字符串类型,统一大小写)
- join 一个key集中的小表 (解决方法:reduce join 改成 map join)
- group by维度过小 某值的数量过多 (解决方法:两阶段聚合,放粗粒度)
- count distinct 某特殊值过多 (解决方法:先用group by)
- 数据频率倾斜——某一个区域的数据量要远远大于其他区域。
- 数据大小倾斜——部分记录的大小远远大于平均值。
如何解决数据倾斜
聚合类group by操作,发生数据倾斜
-
map段部分聚合
- 开启Map端聚合参数设置set hive.map.aggr=true
- 在Map端进行聚合操作的条目数目set hive.grouby.mapaggr.checkinterval=100000
- 有数据倾斜的时候进行负载均衡(默认是false)set hive.groupby.skewindata = true
-
阶段拆分-两阶段聚合 需要聚合的key前加一个随机数的前后缀,这样就均匀了,之后再按照原始的key聚合一次
-
生成的查询计划有两 个 MapReduce 任务。在第一个 MapReduce 中,map 的输出结果集合会随机分布到 reduce 中, 每个 reduce 做部分聚合操作,并输出结果。相同的 Group By Key 有可 能分发到不同的 reduce 中,从而达到负载均衡的目的;第二个 MapReduce 任务再根据预处 理的数据结果按照 Group By Key 分布到 reduce 中(这个过程可以保证相同的 Group By Key 分布到同一个 reduce 中),最后完成最终的聚合操作。
-
假设 key = 水果 select count(substr(a.key,1,2)) as key from( select concat(key,'_',cast(round(10*rand())+1 as string)) tmp from table group by tmp )a group by key
空值产生的数据倾斜
-
1.在查询的时候,过滤掉所有为NULL的数据,比如: SELECT * FROM log a JOIN bmw_users 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.查询出空值并给其赋上随机数,避免了key值为空(数据倾斜中常用的一种技巧) SELECT *FROM log a LEFT JOIN bmw_users b ON CASE WHEN a.user_id IS NULL THEN concat(‘dp_hive’, rand()) ELSE a.user_id END = b.user_id;
Reduce join 改为Map join
- 适用于小表和大表 join,将较小RDD中的数据直接通过collect算子拉取到Driver端的内存中来,然后对其创建一个Broadcast变量;接着对另外RDD执行map类算子,在算子函数内,从Broadcast变量中获取较小RDD 的全量数据,与当前RDD的每一条数据按照连接key进行比对,如果连接key相同的话,那么就将两个RDD的数据用你需要的方式连接起来。
- 设置自动选择MapJoin set hive.auto.convert.join = true;默认为true
- reduce join: 先将所有相同的key,对应的values,汇聚到一个task中,然后再进行join。
- map reduce:broadcast出去那个小表的数据以后,就会在每个executor的block manager中都驻留一份+map算子来实现与join同样的效果。不会发生shuffe,从根本上杜绝了join操作可能导致的数据倾斜的问题;
少用count(distinct),先用group 去重 再count子查询,
-
采用sum() group by的方式来替换count(distinct)完成计算。
-
select count(distinct a) from test ; select count x.a from (select a from test group by a ) x select a, count(distinct b) as c from tbl group by a; select a, count(*) as c from (select a, b from tbl group by a, b) group by a;
特殊值分开处理法
-
当需要把用户表和日志表关联起来时,再日志表中有很多没注册的用户表,可以分开处理
-
select *from (select * from logs where user_id = 0)a join (select * from users where user_id = 0)b on a.user_id = b.user_id union all select * from logs a join users b on a.user_id <> 0 and a.user_id = b.user_id;
大表 join 大表
- 将有大表中倾斜Key对应的数据集单独抽取出来加上随机前缀,另外一个RDD每条数据分别与随机前缀结合形成新的RDD(笛卡尔积,相当于将其数据增到到原来的N倍,N即为随机前缀的总个数)然后将二者Join后去掉前缀。然后将不包含倾斜Key的剩余数据进行Join。最后将两次Join的结果集通过union合并,即可得到全部Join结果。
- RDD扩容
不同数据类型关联产生数据倾斜
-
一张表 s8_log,每个商品一条记录,要和商品表关联。**s8_log 中有字符串商品 id,也有数字的商品 id。**字符串商品 id 类型是 string 的,但商品中的数字 id 是 bigint 的。
-
问题的原因是把 s8_log 的商品 id 转成数字 id 做 Hash(数字的 Hash 值为其本身,相同的字符串的 Hash 也不同)来分配 Reducer,所以相同字符串 id 的 s8_log,都到一个 Reducer 上了。
-
-- 把数字类型转换成字符串类型 SELECT * FROM s8_log a LEFT JOIN r_auction_auctions b ON a.auction_id = CAST(b.auction_id AS string);
多表 union all 会优化成一个 job
- 推广效果表要和商品表关联,效果表中的 auction id 列既有商品 id,也有数字 id,和商品表关联得到商品的信息。
SELECT *
FROM effect a
JOIN (
SELECT auction_id AS auction_id
FROM auctions
UNION ALL
SELECT auction_string_id AS auction_id
FROM auctions
) b
ON a.auction_id = b.auction_id;
-
结论: 这样子比分别过滤数字 id,字符串 id ,然后分别和商品表关联性能要好。这样写的好处:1个 MR 作业,商品表只读取一次,推广效果表只读取一次。把这个 sql 换成 MR 代码的话,map 的时候,把 a 表的记录打上标签 a ,商品表记录每读取一条,打上标签 t,变成两个<key,value> 对,<t,数字id,value>,<t,字符串id,value>。所以商品表的 HDFS(Hadoop Distributed File System) 读只会是一次。
-
问题:比如推广效果表要和商品表关联,效果表中的 auction_id 列既有 32 为字符串商 品 id,也有数字 id,和商品表关联得到商品的信息。 比分别过滤数字 id,字符串 id 然后分别和商品表关联性能要好。
SELECT * FROM effect a
JOIN
(SELECT auction_id AS auction_id FROM auctions
UNION All
SELECT auction_string_id AS auction_id FROM auctions) b
ON a.auction_id=b.auction_id;
- 场景:有一张user表,为卖家每天收到表,user_id,ds(日期)为key,属性有主营类目,指标有交易金额,交易笔数。每天要取前10天的总收入,总笔数,和最近一天的主营类目。
SELECT user_id, substr(MAX(CONCAT(ds, cat)), 9) AS main_cat, SUM(qty), SUM(amt) FROM users
WHERE ds BETWEEN 20120301 AND 20120329
GROUP BY user_id
优化in/exists语句
- hive1.2.1也支持in/exists操作,但还是推荐使用hive的一个高效替代方案:left semi join
排序选择
- cluster by: 对同一字段分桶并排序,不能和sort by连用;
- distribute by + sort by: 分桶,保证同一字段值只存在一个结果文件当中,结合sort by 保证每个reduceTask结果有序;
- sort by: 单机排序,单个reduce结果有序
- order by:全局排序,缺陷是只能使用一个reduce