大数据数仓面试:数据倾斜

01Map倾斜

为什么

Map的主要功能是从磁盘中将数据读入内存,由于读入数据的文件大小分布不均匀,因此会导致有些map task读取并且处理的数据特别多,而有些map task处理的数据特别少,造成map端长尾。以下两种情况可能会导致Map端长尾:

  • 上游表文件的大小特别不均匀,并且小文件特别多
  • Map端做聚合时,比如map join,某些map task读取文件的某个值特别多

怎么办

针对第一种情况,可以对上游的小文件进行合并,通常就是调整小文件的参数来进行优化,比如 调节map任务的map task的数量,以及 调节单个map task读取的小文件个数。

针对第二种情况:

-- 获取手机APP日志明细中的前一个页面的页面信息

select ...

from (

select

ds,

unique_id,

pre_page

from tmp_app_ut_1

where ds = '${bizdate}'

and pre_page is not null

-- 优化如下

distribute by rand()

) a

left join (

select *

from page_ut

where ds = '${bizdate}'

and is_enable = 'Y'

) b

on 1=1

where a.pre_page rlike b.page_type_rule;

我们可以通过distribute by rand()将map端分发后的数据重新按照随机值再进行一次分发。那么原先不加随机分发函数时,map阶段需要与使用mapjoin的小表进行笛卡尔积操作,map端完成了大小表的分发和笛卡尔积操作。使用随机分发函数后,map端只负责数的分发,不再有复杂的聚合或者笛卡尔积操作,因此不会导致map端长尾。

图片源于网络,侵删

总结套路

在开发过程中如果遇到map端长尾的情况,首先考虑如何让map task读取的数据量足够均匀,然后判断是哪些操作导致map task比较慢,最后考虑这些操作是否必须在map端完成,在其他阶段是否会做得更好。

02Join倾斜

为什么

Join操作需要参与Map和Reduce的整个阶段,这里以一段SQL为例来看Join的整个过程

select student_id, student_name, course_id

from student

left join student_score

on student.student_id = student_score.student_id

这里主要介绍三种常见的Join倾斜场景

  • Join的某张表输入比较小,可以采用MapJoin
  • Join的每张表输入都较大,且长尾是空值导致的,可以将空值处理成随机值
  • Join的每张表输入都较大,且长尾是热点值导致的,可以对热点值和非热点值分别进行处理,再合并数据

怎么办

针对第一种情况:如果某张表输入比较小,则可以采用mapjoin避免倾斜

  • mapjoin原理:将reduce操作提前到map端执行,将小表读入内存,顺序扫描大表完成join。
  • 使用方法:在select后加上/*+mapjoin(a)*/即可,其中a代表小表;如今大数据平台一般可以自动选择是否使用mapjoin,不需要显式设置

针对第二种情况:

  • 数据表中经常出现空值的数据,如果关联key为空值且数据量比较大,join时就会因为空值的聚集导致长尾,针对这种情况可以将空值处理成随机值。因为空值无法关联上,只是分发到一处,因此处理成随机值既不会影响关联结果,也能很好的避免聚集导致长尾
  • 使用方法:

select ...
from t1
left join t2
on coalesce(t1.key, rand()*9999) = t2.key

针对第三种情况:

  • 如果是因为热点值导致的长尾,并且join的输入比较大无法使用mapjoin,则可以先将热点key取出,对于主表数据用热点key切分成热点数据和非热点数据两部分分别处理,最后合并。
  • 使用方法:这里以淘宝的PV日志表关联商品维表为例进行介绍
    • 获取热点key:将PV大于50000的商品id取出到临时表中

insert overwrite table topk_item
select item_id
from (
select
item_id,
count(1) cnt
from pv
where ds='${bizdate}'
and item_id is not null
group by item_id
) a
where cnt >= 50000

    • 获取热点数据
      • 将pv表和热点key表关联,取到热点商品的日志数据。同时,将商品维表和热点key表关联,取到热点商品的维表数据。然后将两部分数据进行关联。

select /*+MAPJOIN(a)*/
...
from (
select /*+MAPJOIN(t1)*/
t2.*
from (
select item_id
from tokp_item
where ds = '${bizdate}'
) t1
join (
select *
from pv
where ds = '${bizdate}'
and item_id is not null
) t2
on t1.item_id = t2.item_id
) l
left join (
select /*+MAPJOIN(t1)*/
t2.*
from (
select item_id
from tokp_item
where ds = '${bizdate}'
) t1
join (
select *
from item
where ds = '${bizdate}'
) t2
on t1.item_id = t2.item_id
) a
on a.item_id = l.item_id

      • 获取非热点数据
        • 将pv表和热点key表进行外关联,key为null的数据即非热点商品的日志数据。然后再关联商品维表

select ...
from (
select
from (
select item_id
from topk_item
where ds = '${bizdate}'
) t1
right join (
select *
from pv
where ds = '${bizdate}'
) t2
on t1.item_id = t2.item_id
where t1.item_id is null
) l
left join (
select *
from item
where ds = '${bizdate}'
) a
on l.item_id = a.item_id

      • 将上面取到的热点数据和非热点数据通过union all合并后即可得到完整的日志数据,并关联了商品信息
03Reduce倾斜

为什么

reduce端负责的是对map端梳理后的有序k-v键值对进行聚合,即进行count、sum、avg等聚合操作。产生长尾的主要原因就是 key的数据分布不均匀,常见的几种情况如下:

  • map端直接做聚合时出现key值分布不均匀
  • 动态分区数过多时可能造成小文件过多,从而引起reduce端长尾
  • 多个distinct同时出现在一段SQL代码中时,数据会被分发多次,不仅会造成数据膨胀N倍,还会把长尾现象放大N倍

怎么办

针对第一种情况,参考join倾斜部分

针对第二种情况:

  • 背景:假如有K个map task,N个目标分区,那么最坏的情况下,可能产生KxN个小文件
  • 解决办法:把相同的目标分区交由同一个reduce task来写入,避免小文件过多

针对第三种情况:

  • 背景:在7天、30天等时间范围内,分PC端、无线端、所有终端,计算支付买家数和支付商品数,其中支付买家数和支付商品数都需要去重。因为需要根据日期、终端等多种条件组合对买家和商品进行去重计算,因此有6个count distinct计算。
  • 解决办法:以计算支付买家数为例,可以分两次进行查询,先执行group by 原粒度+buyer_id,计算出所有口径下的买家支付的次数(不去重),然后再执行group by原粒度,当上一步的count值大于0时,说明这一买家在这个统计口径下有过支付,计入支付买家数,否则不计入。

总结套路

重点关注一下multi distinct的情况,如果出现多个需要去重的指标,那么在不同指标join在一起之前,一定确保指标的粒度时原始表的数据粒度。

来源:三石大数据

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值