面试聊数仓第二季之计算优化
- Map的主要功能是从磁盘中将数据读入内存,由于读入数据的文件大小分布不均匀,因此会导致有些map task读取并且处理的数据特别多,而有些map task处理的数据特别少,造成map端长尾。以下两种情况可能会导致Map端长尾:
- 上游表文件的大小特别不均匀,并且小文件特别多
- Map端做聚合时,比如map join,某些map task读取文件的某个值特别多
1.计算优化
主要从数据倾斜方面讨论任务优化
Map倾斜
为什么
怎么办
针对第一种情况,可以对上游的小文件进行合并,通常就是调整小文件的参数来进行优化,比如 调节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
怎么办
/*+mapjoin(a)*/
即可,其中a代表小表;如今大数据平台一般可以自动选择是否使用mapjoin,不需要显式设置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
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端负责的是对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在一起之前,一定确保指标的粒度时原始表的数据粒度。