原理以及现象
先来解释一下,出现什么现象的时候我们认定他为数据倾斜,以及他数据倾斜发生的原理是什么?
数据倾斜产生的现象:
比如一个spark任务中,绝多数task任务运行速度很快,但是就是有那么几个task任务运行极其缓慢,慢慢的可能就接着报内存溢出的问题了,那么这个时候我们就可以认定他是数据倾斜了。
数据倾斜产生的原因:
接下来说一下发生数据倾斜的底层理论,其实可以非常肯定的说,数据倾斜就是发生在shuffle类的算子中,在进行shuffle的时候,必须将各个节点的相同的key拉到某个节点上的一个task来进行处理,比如按照key进行聚合和join操作等,这个时候其中某一个key数量量特别大,于是就发生了数据倾斜了。
我们知道了导致数据倾斜的问题就是shuffle算子,所以我们先去找到代码中的shuffle的算子,比如distinct、groupBYkey、reduceBykey、aggergateBykey、join、cogroup、repartition等,那么问题一定就出现在这里。
找到shuffle类的算子之后,我们知道一个application分为job,那么一个job又划分为多个stage,stage的划分就是根据shuffle类的算子,也可以说是宽依赖来划分的,所以这个时候我们在spark UI界面上点击查看stage
这个时候我们找到了数据倾斜发生的地方了,但是我们还需要知道到底是哪个key数据量特别大导致的数据倾斜,于是接下来来聊一聊这个问题。
寻找最大数据量的key:
他就是从所有key中,把其中每一个key随机取出来一部分,然后进行一个百分比的推算,学过采样算法的都知道,这是用局部取推算整体,虽然有点不准确,但是在整体概率上来说,我们只需要大概之久可以定位那个最多的key了。
解决数据倾斜的方案
解决方案一: 提高shuffle 的并行度
我们知道在reduceByKey中,shuffle read task的值默认为200,也就是说用两百个task来处理任务,对于我们一个很大的集群来说,每个task的任务中需要处理的key也是比较多的,所以我们把这个数量提高一些比如设置reduceByKey(1000) ,这个时候task的数量就多了,然后分配到每个task的key就少了,这样并行度就提高了,但总体来说这个办法对于数量特别大的key来说效果不是特别好,他也有一定的局限性,
解决方案二:两阶段聚合(局部聚合+全局聚合)
这个办法可以解决大部分的数据倾斜问题:
假如我们有一个rdd,其中每一个key 的数量特别大,我们进行shuffle的时候速度比较慢,比如说每个key的就是hello,有10000条,单一的进行shuffle肯定耗时比较长,所以我们可以给他打10以内的随机数,然后这时候进行局部的预聚合,然后再去掉之前添加的前缀,再进行聚合,reduceByKey,
0_hello,1
1_hello,1
2_hello,1
3_hello,1
0_hello,1
2_hello,1
3_hello,1
0_hello,3000
1_hello,2000
2_hello,2500
3_hello,2500
最后结果
hello,10000
当然这种方法还是有局限性,他适用于聚合类的操作,如果对于join的操作还是不行的
解决方案三:将reduce join转整map join
条件的这种方法有假定的前提,比如有两个rdd进行join操作,其中一个rdd,的数量不是很大,比如低于1G的情况,
具体操作就是选择两个rdd中哪个数据比较小的,然后我们把它拉到driver端,然后再通过广播变量.的方式广播出去,这个时候在进行join的话,因为数据都是在同一个executor中,所以shuffle中不会有数据的传输,也就避免了数据倾斜,所以这种方式很好,
当然了,这种方法也是有缺陷的,比如两个rdd都非常大,比如超过了10个G,这个时候我们就不能用这种方法了,因为数据量太大了,广播变量还是需要太大的消耗,我们还需要继续探索更深层次的解决办法。于是就有下面这种不可思议的解决办法。
解决方案四:采样倾斜key并分拆join操作
注意:再讲解本方法之前,我还需要说明一点,如果您没看懂就多看一次,因为这种法方法太不可思议了,真的要明白还是需要一点想想力的,我尽量把它表达的明白些。
在方法五中,我们通过广播的方式可以解决大小表join(小表低于5G以下)的操作,但是这个方法是解决超过10个G以上两个大表JOIN,比如50个G的数据都可以。
实现思路:
1.对包含少数几个数据量过大的key的那个Rdd,通过sample算子采样出一份样本来,然后统计以下每个key的数量,计算出来数据量最大的是哪几个key。
2.然后将这几个key对应的数据从原来的rdd中拆分出来,形成一个单独的rdd,并给每个key都打上n以内的随机数作为前缀,而不会导致倾斜的大部分key形成另外一个rdd。
3.接着将需要join的另外一个rdd,也过滤出来那几个倾斜的key对应的数据并形成一个单独的rdd,将每条数据膨胀成n条数据,这n条数据都按顺序附加一个0~n的前缀,不会导致倾斜的大部分key也形成另外一个rdd。
4.再将附加了随机前缀的独立rdd与另外一个膨胀n倍的独立rdd进行join,此时就可以将原先相同的key打算成n份,分散到多个task中去进行join了。
5.而另外两个普通的rdd就照常join即可。
6.最后将两次join的结果使用union算子合并起来即可,就是最终join结果