1.什么是数据倾斜

  由于数据分布不均匀,造成数据大量的集中到一点,造成数据热点;简单来说就是key的分化严重不均,造成一部分数据很多,一部分数据很少的情况;

  比如:wordcount,最后的输出阶段形成了('aaa',1)这种格式,然后在reduce阶段进行value的增加操作,最后计算出value的总次数;若进行wordcount的文本有100G,其中80G全部时‘aaa’,剩下20G根据key不同分散到不同的reduce,进行相加的情况,如造成了数据倾斜;

  

2.hadoop框架的特征

  (1)不怕数据大,怕数据倾斜;

  (2)jobs数比较多的作业运行效率相对比较低,如子查询比较多;

  (3)sum,count,max,min等聚合函数,通常不会有数据倾斜问题;

3.主要表现

  任务进度长时间维持在99%或者100%的附近,查看任务监控页面,发现只要少量reduce子任务完成,因为其处理的数据量和其他的reduce差异过大;单一reduce处理的记录数和平均记录数相差太大,通常达到好几倍之多,最长时间远大于平均时长;

  

4.易产生数据倾斜的情况

  

  (1)group by不和聚集函数搭配使用的时候l;

  (2)count(distinct)在数据量大的情况下,容易数据倾斜,因为count(distinct)是按group by字段分组,按distinct字段排序;

  (3)小表关联超大表join;

5.业务场景

  5.1 空值产生的数据倾斜

    场景:在日志中,常常会有信息丢失问题,比如日志中user_id,如果取其中user_id和用户表中的user_id相关联,就会碰到数据倾斜的问题;

    解决方案:

      方式一:user_id为空的不参与关联

#以下HQL字句为伪案例,意在表明案例
select * from log a join user b on a.user_id is not null and a.user_id = b.user_id
union all
select * from log c where c.user_id is null;

      方式二:赋予user_id为空的列新值

select * from log a left outer join user b on
case when a.user_id is null then concat('hive',rand()) else a.user_id end = b.user_id

      方式一和方式二直接的对比:

        方式二比方式一效率更好,不但IO少了,而且作业书也少了,方式一种log表读了两次,jobs肯定2,而方式二是1,这个优化适合无效id产生的数据倾斜,把控制的key变成一个字符串加上一个随机数,就能把造成数据倾斜的数据分到不同的reduce上解决数据倾斜的问题;

        改变之处,是本身为null的所有记录不会拥挤在同一个reduceTask了,会由于有替代的随机字符串值,而分散了多个reduceTask中了,由于null值关联不上,处理后并不影响最终结果;

  5.2 不同数据类型关联产生的数据倾斜

    场景:用户表中user_id字段为int,log表中user_id为既有string也有int类型,当按照两个表的user_id进行join操作的时候,默认的hash操作会按照int类型的id进行分配,这样就会导致所有的string类型的id就被分到同一个reducer当中;

    解决方案:

select * from user a left outer join log b on b.user_id = cast(a.user_id as string);

  5.3 大小表关联

    注意:使用map join解决小表关联大表造成的数据倾斜问题;这个方法使用的频率很高;

    map join概念:将其中做连接的小表(全量数据)分发到所有mapTask端进行join,从而避免了reduceTask,前提要求是内存足以装下该全量数据;

      

     以大表a和小表b为例,所有的mapTask节点都装载小表b的所有数据,然后大表a的一个数据块数据比如说是a1去跟b全量数据做连接,就省去了reduce做汇总的过程;所有相对来说,在内存允许的条件下使用mapjoin比直接使用MapReduce效率还高些,当然这只限于join查询的时候;

    在hive,直接提供了能够在HQL语句指定该次查询使用map join,map join的用法是在查询/子查询的select关键字后面添加/*+MAPJOIN(tablelist)*/提示优化器转化为mapjoin;气筒tablelist可以是一个表,或是以逗号连接的表的列表;tablelist中的表将会读入内存,通常应该是将小表写在这里;

    mapjoin具体用法:

select /* +mapjoin(a) */ a.id aid, name, age from a join b on a.id = b.id;

    在hive0.11版本以后会自动开启map join优化,由以下两个参数控制:

set hive.auto.convert.join=true; #设置 MapJoin 优化自动开启
set hive.mapjoin.smalltable.filesize=25000000 #设置小表不超过多大时开启 mapjoin 优化

    如果是大表关联大表呢?

      那就将大表分割成为小表,然后使用map join;

    使用map join解决小表(记录数少)关联大表的数据倾斜问题,这个方法使用的频率非常高,但如果小表很大,大到map join会出现bug或异常,这时就需要特别的处理;

    举例:日志表和用户表做关联

select * from log a left outer join users b on a.user_id = b.user_id;

      user表有600w+的记录,把users分发到所有的map上也是个不小的开销,而且map join不支持这么大的下表;如果用普通的join,又会碰到数据倾斜的问题;

      改进方案:

select /*+mapjoin(x)*/* from log a #大表--->小表
left outer join (
 select /*+mapjoin(c)*/ d.*
 from ( select distinct user_id from log ) c join users d on c.user_id = d.user_id 
) x  on a.user_id = x.user_id;

 

 posted on 2020-04-25 14:41  wnwn  阅读(184)  评论(0编辑  收藏  举报