一、数据倾斜
1、什么是数据倾斜?Hadoop 框架的特性决定最怕数据倾斜
由于数据分布不均匀,造成数据大量的集中到一点,造成数据热点。
节点间数据分布不均衡,会造成 map 端每个 map 任务的工作量不同,即 map 端数据倾斜。
Map-reduce,把相同 key 提交给同一个 reduce,如果 key 不均衡就会造成不同的 reduce 的
工作量不同。 以京东首页活动为例,曝光率大的是大活动,曝光率小的是小活动:
假如 reduce1 处理的是小活动,reduce2 处理大活动,reduce2 干的活比其他 reduce 多很多, 会出现其他 reduce 执行完毕了,reduce2 还在缓慢执行。
症状:map 阶段快,reduce 阶段非常慢; 某些 map 很快,某些 map 很慢; 某些 reduce 很快,某些 reduce 奇慢。
如下情况: A、数据在节点上分布不均匀
B、join 时 on 关键词中个别值量很大(如 null 值)
C、count(distinct ),在数据量大的情况下,容易数据倾斜,因为 count(distinct)是按 group by 字
段分组,按 distinct 字段排序。
其中 A 无法避免。B 见后边的 Join 章节。C 语法上有时无法避免
如何解决数据倾斜?实际上是没办法避免的,这里的解决只是个别情况起效: 有数据倾斜的时候进行负载均衡
set hive.groupby.skewindata = false;
当选项设定为 true,生成的查询计划会有两个 MR Job。第一个 MR Job 中,Map 的输出结果会随机分布到 Reduce 中,每个 Reduce 做部分聚合操作,并输出结果,这样处 理的结果是相同的 Group By Key 有可能被分发到不同的 Reduce 中,从而达到负载均衡的 目的;第二个 MR Job 再根据预处理的数据结果按照 Group By Key 分布到 Reduce 中(这 个过程可以保证相同的 Group By Key 被分布到同一个 Reduce 中),最后完成最终的聚合操 作。
二、 Join 、MapJoin、Group by
Join
按照 join 的 key 进行分发,而在 join 左边的表的部分数据会首先读入内存,如果左边表 的 key 相对分散(或少,分散的意思是相同 key 的数据量小),join 任务执行会比较快;而如 果左边的表 key 比较集中(key 的大小量级分化),而这张表的数据量很大,那么数据倾斜就 会比较严重。
Map 阶段同一 Key 数据分发给同一个 reduce。
Join 原则:
小表 Join 大表,原因是在 Join 操作的 Reduce 阶段(不是 Map 阶段),位于 Join 左
边的表的内容会被加载进内存,将条目少的表放在左边,可以有效减少发生内存溢出的几率。 多个表关联时,最好分拆成小段,避免大 sql(无法控制中间 Job)。
多表 Join on 条件相同时合并为一个 Map-Reduce,做 OUTER JOIN 的时候也是一样,查看 执行计划 explain
比如查询,3 个表关联:
select pt.page_id,count(t.url) PV from rpt_page_type pt
join
(select url_page_id,url from trackinfo where ds='2013-10-11') t on pt.page_id=t.url_page_id
join
(select page_id from rpt_page_kpi_new where ds='2013-10-11') r on t.url_page_id=r.page_id
group by pt.page_id;
比较 2 个表关联:
select pt.page_id,count(t.url) PV from rpt_page_type pt
join
(select url_page_id,url from trackinfo where ds='2013-10-11') t on pt.page_id=t.url_page_id
group by pt.page_id;
利用这个特性,可以把相同 join on 条件的放在一个 job 处理。
如果 Join 的条件不相同,如:
5000 万,纬度表 每日 2 亿,按日增量表
INSERT OVERWRITE TABLE page_pv select pt.page_id,count(t.url) PV from rpt_page_type pt
join
(select url_page_id,url,province_id from trackinfo where ds='2013-10-11') t on pt.page_id=t.url_page_id
join
(select page_id,province_id from rpt_page_kpi_new where ds='2013-10-11') r on t.province_id=r.province_id
group by pt.page_id;
大表 Join 大表
访户未登录时,日志中 userid 是空,在用 user_id 进行 hash 分桶的时候,会将日志中 userid 为空的数据分到一起,导致了过大空 key 造成倾斜。
解决办法:
把空值的 key 变成一个字符串加上随机数,把倾斜的数据分到不同的 reduce 上, 由于 null 值关联不上,处理后并不影响最终结果
案例:
End_user
Trackinfo
原写法:
select u.id,t.url,t.track_time from end_user u
join
(select end_user_id,url,track_time from trackinfo where ds='2013-12-01') t on u.id=t.end_user_id limit 2;
调整为:
select u.id,t.url,t.track_time from end_user u
join
(select case when end_user_id='null' or end_user_id is null
then cast (concat('00000000_',floor(rand()*1000000)) as bigint) else end_user_id end end_user_id ,
url,track_time
from trackinfo where ds='2013-12-01') t on u.id=t.end_user_id limit 2;
此例子只是为了说明原理。
当前这个场景不需要这么麻烦,如下即可: select u.id,t.url,t.track_time
from end_user u
select ...
from
(select *
from tbcdm.dim_tb_itm
where ds='${bizdate}'
)son1
left outer join
(select *
from tbods.s_standard_brand
where ds='${bizdate}'
and status=3
)son2
on coalesce(son1.org_brand_id,rand()*9999)=son2.value_id
count(distinct)的优化策略
-- 第三层SELECT
SELECT
SUM(s.mau_part) mau
FROM
(
-- 第二层SELECT
SELECT
tag,
COUNT(*) mau_part
FROM
(
-- 第一层SELECT
SELECT
uuid,
CAST(RAND() * 100 AS BIGINT) tag -- 为去重后的uuid打上标记,标记为:0-100之间的整数。
FROM detail_sdk_session
WHERE partition_date >= '2016-01-01' AND partition_date <= now
GROUP BY uuid -- 通过GROUP BY,保证去重
) t
GROUP BY tag
) s
;
第一层SELECT:对uuid进行去重,并为去重后的uuid打上整数标记
第二层SELECT:按照标记进行分组,统计每个分组下uuid的个数
第三层SELECT:对所有分组进行求和
上面这个方法最关键的是为每个uuid进行标记,这样就可以对其进行分组,分别计数,最后去和。如果数据量确实很大,也可以增加分组的个数。例如:CAST(RAND() * 1000 AS BIGINT) tag
这个可以很好的利用分布式的原理进行统计