面试聊数仓第二季之计算优化

    1.计算优化

    主要从数据倾斜方面讨论任务优化

    Map倾斜

    为什么

  • 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端完成,在其他阶段是否会做得更好。

    Join倾斜

    为什么

    • 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合并后即可得到完整的日志数据,并关联了商品信息
  • 总结套路

    • 在开发过程中如果遇到join倾斜的情况,首先分析两张表的大小,如果有小表,首选mapjoin;如果都是大表,那么就需要分析大表key值的分布;如果空值较多,则将其处理成随机值;如果存在热点值,则找热点key,分别获取热点数据和非热点数据,然后进行union all即可。

    Reduce倾斜

    为什么

  • 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在一起之前,一定确保指标的粒度时原始表的数据粒度。
#数仓面试##数据人的面试交流地##如何判断面试是否凉了##软件开发薪资爆料#
全部评论

相关推荐

5 21 评论
分享
牛客网
牛客企业服务