目录
hive outline
hive 空值引发的数据倾斜
大量的null值经hash取余后结果是一样的,会被标记到到同一分区,进而会被分配到一个reduce中,产生数据倾斜
解决方案:
第一种:null值没有用,可以直接对null值进行过滤
SELECT *
FROM log a
JOIN 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;
第二种:null值有用(生产中,有些空值是具备业务含义的,比如:剩余还款字段,用户某个时间点已还清贷款,数据为空,但去掉空值数据是肯定不对的,所以需要具体情况具体分析),又分为2种情况
(1)当存在很多Null值的时候,可以选择把空值数据单独筛选出来,剩余非空数据正常Join,最后再把空值数据union all上去即可
(2)特使情况下可以给Null赋上随机值,这样它们的hash结果就不一样,最终就会进到不同的reduce中
SELECT *
FROM log a
LEFT JOIN users b ON CASE
WHEN a.user_id IS NULL THEN concat('hive_', rand())
ELSE a.user_id
END = b.user_id;
如果A、B两表join操作,假如A表中需要join的字段为null,但是B表中需要join的字段不为null,这两个字段根本就join不上啊,为什么还会放到一个reduce中呢?
hive 数据类型不同引发的数据倾斜
比如用户表中 user_id 字段为 int,日志 表(logs)中 user_id 字段既有 string 类型也有 int 类型。当按照 user_id 进行两个表的 Join 操作时,默认的 Hash 操作会按 int 型的 id 来进行分区,这样会导致所有 string 类型 id 记录都会被分配到同一个分区中,进入到同一个 Reducer 中
解决方案:
如果key字段既有string类型也有int类型,默认的hash就都会按int类型来分配,那我们直接把int类型都转为string就好了,这样key字段都为string,hash时就按照string类型分配了:
select *
from users a
left outer join logs b on a.usr_id = cast(b.user_id as string)
hive 数据文件太大且不可切割引发的数据倾斜
当集群的数据量增长到一定规模,有些数据需要归档或者转储,这时候往往会对数据进行压缩;当对文件使用GZIP压缩等不支持文件分割操作的压缩方式,在日后有作业涉及读取压缩后的文件时,该压缩文件只会被一个任务所读取。如果该压缩文件很大,则该Map任务可能会成为作业运行的瓶颈。这种情况也就是Map读取文件的数据倾斜
解决方案:
这种数据倾斜问题没有什么好的解决方案,只能将使用GZIP压缩等不支持文件分割的文件转为bzip2和zip等支持文件分割的压缩方式
hive 数据膨胀引发的数据倾斜
在多维聚合计算时,如果进行分组聚合的字段过多,如下:
select a,b,c,count(1)
from log
group by a,b,c
with rollup;
注:
with rollup是用来在分组统计数据的基础上再进行统计汇总,即用来得到group by的汇总信息
如果上面的log表的数据量很大,并且Map端的聚合不能很好地起到数据压缩的情况下,会导致Map端产出的数据急速膨胀,这种情况容易导致作业内存溢出的异常。如果log表含有数据倾斜key,会加剧Shuffle过程的数据倾斜
解决方案:
可以拆分上面的sql,将with rollup拆分成如下几个sql:
SELECT a, b, c, COUNT(1)
FROM log
GROUP BY a, b, c;
SELECT a, b, NULL, COUNT(1)
FROM log
GROUP BY a, b;
SELECT a, NULL, NULL, COUNT(1)
FROM log
GROUP BY a;
SELECT NULL, NULL, NULL, COUNT(1)
FROM log;
但是,上面这种方式不太好,因为现在是对3个字段进行分组聚合,那如果是5个或者10个字段呢,那么需要拆解的SQL语句会更多。
在Hive中可以通过参数 hive.new.job.grouping.set.cardinality 配置的方式自动控制作业的拆解,该参数默认值是30。表示针对grouping sets/rollups/cubes这类多维聚合的操作,如果最后拆解的键组合大于该值,会启用新的任务去处理大于该值之外的组合。如果在处理数据时,某个分组聚合的列有较大的倾斜,可以适当调小该值
hive 中最常见的数据倾斜
在一些操作中,当使用 collect_list 函数时,中间结果会放到内存中,所以如果collect_list聚合太多数据,会导致内存溢出:
-- collect_list:将分组中的某列转为一个数组返回
select s_age, collect_list(s_score) list_score
from student
group by s_age
解决方案:
一般情况下使用group by 的负载均衡机制就可以解决该问题,但这里不推荐选用group by 的负责均衡机制
,先了解一下该机制的原理:
该机制会在group by时启动两个mr job。第一个mr会将map端数据随机分区后送往到各个reducer,每个 reducer做部分聚合。第二个mr再将前面预处理过的数据,按照key再次聚合并输出结果
group by 的负责均衡机制的核心作用
在于生成的第一个mr能够有效减少数量。但是对于collect_list
这类要求全量操作所有数据中间结果的函数来说,明显起不到作用,反而因为引入新的作业增加了磁盘和网络I/O的负担,而导致性能变得更为低下,一般来说
这类问题最直接的方式就是调整reduceTask的内存大小
除collect_list这类函数
外推荐使用group by 的负责均衡机制
-- 开启group by 的负责均衡机制
hive.groupby.skewindata=true;
- 不影响业务的条件下,也可以使用这种方案:开启Map端聚合
-- 开启Map端聚合:Combiner
hive.map.aggr=true;
-- 在Map端进行聚合操作的条目数目
hive.groupby.mapaggr.checkinterval = 100000
适用条件:
不影响业务的条件下(例如:求和可以使用,求平均值就不能使用)
原理:
使用combiner 后相当于在map端提前进行 reduce,即把一个 Mapper 中的相同 key 先进行了聚合,减少 shuffle 过程中传输的数据量,以及后续Reduce 端的计算量
Combiner触发时机:
- 溢写文件之前会触发一次Combiner
- 对多个溢写文件进行merge时,也会触发一次Combiner
- 也可以使用这种方案:实现自定义分区
原理:
根据数据分布情况,自定义散列函数,将 key 均匀分配到不同 Reducer
大表join大表
未经优化的大表与大表JOIN
优化后的大表与大表JOIN
测试
(1)2个大表
(2)左表随机,右表扩容(解决大key倾斜)
select *
from (
select id,
concat(id, '-', ceil(rand() * 2)) as id_random,
name
from t1_skew
) a
join
(
select id,
concat(id, '-', 1) as id_random,
name
from t2_skew
union all
select id,
concat(id, '-', 2) as id_random,
name
from t2_skew
) b
on a.id_random = b.id_random;