1.什么是数据倾斜
某些地方特别多,某些地方又特别少,导致的在处理数据的时候,有些很快就处理完了,而有些又迟迟未能处理完,导致整体任务最终迟迟无法完成,这种现象就是数据倾斜。
数据倾斜就是由于数据的"分布不平衡",导致mapreduce的任务的多个reduce中,其中有一个或者若干个reduce要处理的数据量特别大,而其他的reduce处理的数据量则比较小,那么这些数据量小的reduce很快就可以完成,而数据量大的则需要很多时间,导致整个任务一直在等它而迟迟无法完成。
跑mapreduce任务时常见的reduce的进度总是卡在99%,点开之后发现只有少量的reduce任务没有完成,这种现象很大可能就是数据倾斜造成的。
实际上,数据倾斜不一定都会出现在reduce阶段,实际上也可能出现在map阶段。因为一般来说在MR任务中,针对每一个文件都会有一个maptask,所以如果某个文件过大,也会导致某个map执行时间过长。
2.数据倾斜的原因
1)数据分区不合理,某个文件数据量过大,导致某个map任务执行时间过长
2) key的分布不均匀或者说某些key太集中。
reduce的数据量大小差异过大,而reduce的数据是分区的结果,分区是对key求hash值,根据hash值决定该key被分到某个分区,进而进入到某个reduce,而如果key很集中或者相同,那么计算得到它们的hash值可能一样,那么就会被分配到同一个reduce,就会造成这个reduce所要处理的数据量过大。
3)业务数据自身的特性。
比如某些业务数据作为key的字段本就很集中,那么结果肯定会导致数据倾斜啊。
4)某些SQL语句本身就有数据倾斜
其实从根本上来说,根本原因就是key的分布不均匀导致的。只不过导致key的分布不均匀的原因有很多,下面会讲到。
3.数据倾斜的解决方案
3.1 设置参数
-
设置hive.map.aggr=true
开启map端部分聚合功能,就是将key相同的归到一起,减少数据量,这样就可以相对地减少进入reduce的数据量,在一定程度上可以提高性能,当然,如果数据的减少量微乎其微,那对性能的影响几乎没啥变化。
-
设置hive.groupby.skewindata=true
如果发生了数据倾斜就可以通过它来进行负载均衡。当选项设定为 true,生成的查询计划会有两个 MR Job。第一个 MR Job 中,Map 的输出结果集合会随机分布到 Reduce 中,每个 Reduce 做部分聚合操作,并输出结果,这样处理的结果是相同的Key 有可能被分发到不同的 Reduce 中,从而达到负载均衡的目的;第二个 MR Job 再根据预处理的数据结果按照Key 分布到 Reduce 中(这个过程是按照key的hash值进行分区的,不同于mr job1的随机分配,这次可以保证相同的Key 被分布到同一个 Reduce 中),最后完成最终的聚合操作。所以它主要就是先通过第一个mr job将key随机分配到reduce,使得会造成数据倾斜的key可能被分配到不同的reduce上,从而达到负载均衡的目的。到第二个mr job中,因为第一个mr job已经在reduce中对这些数据进行了部分聚合(就像单词统计的例子,a这个字母在不同的reduce中,已经算出它在每个reduce中的个数,但是最终的总的个数还没算出来,那么就将它传到第二个mr job,这样就可以得到总的单词个数),所以这里直接进行最后的聚合就可以了。
3.2 优化sql
-
两表join(小表join大表)
当进行两表join的时候,发现某个表是属于一个小表,默认是表的数据文件大小小于某个值的时候。那么就可以开启map-side join来进行优化。
-
某个表中空值过多
当一张表中的空值过多的时候,所有的空值都会由一个reduce执行。这时候就可以为每一个空值进行赋值,可以由一个指定的字符串+随机数的方式赋值。
-
大表变小表
当表都是很大的时候,如果我们仅仅是需要用到其中的一小部分数据,那我们可以直接取出来,做一张小表
-
不同数据类型关联
场景:用户表中user_id字段为int,log表中user_id字段既有string类型也有int类型。
当按照user_id进行两个表的Join操作时,因为我们在连接时要进行user_id的比较,所以需要user_id的类型都相同,如果我们选择将log表中的String类型转换为int类型,那么就可能会出现这种情况:String类型转换为int类型得到的都是null值(这就是类型转换的问题了,String类型数据转换为int类型会失败,数据丢失,就会赋null值),如果所有的String类型的user_id都变成了null,那么就又出现了集中的key,分区后就又会导致数据倾斜。所以我们进行类型转换时不能选择将String类型转换为int,而应该将int类型转换为String,因为int转换为String不会出问题,int类型原来的值是什么,转换为String后对应的字符串就会是什么,形式没变,只是类型变了而已。
解决方法:把int类型转换成字符串类型
-
count(distinct xxx)产生数据倾斜
当某表中的key很集中的时候,使用count(distinct xxx)同样会导致数据倾斜。
解决方法:
1)先找出这个集中的key,将其过滤掉,直接计算其他的,再加上它就行了 -
group by 产生数据倾斜
这个产生的原因和count(distinct xxx)产生的很类似。
解决策略是对key值进行加盐处理:
核心实现思路就是进行两阶段聚合。第一次是局部聚合,先给每个key都打上一个随机数,比如10以内的随机数,此时原先一样的key就变成不一样的了,比如(hello, 1) (hello, 1) (hello, 1) (hello, 1),就会变成(1_hello, 1) (1_hello, 1) (2_hello, 1) (2_hello, 1)。接着对打上随机数后的数据,执行sum,count等聚合操作,进行局部聚合,那么局部聚合结果,就会变成了(1_hello, 2) (2_hello, 2)。然后将各个key的前缀给去掉,就会变成(hello,2)(hello,2),再次进行全局聚合操作,就可以得到最终结果了,比如(hello, 4)。