吃透数据倾斜

原理以及现象

先来解释一下,出现什么现象的时候我们认定他为数据倾斜,以及他数据倾斜发生的原理是什么?

数据倾斜产生的现象:

比如一个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结果

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ConcurrentHashMap是Java中的一个线程安全的哈希表实现,它是基于散列的数据结构,可以支持并发的读和更新操作。它相比于普通的HashMap,在多线程环境下具有更好的性能和可靠性。 ConcurrentHashMap的主要特点如下: 1. 线程安全:ConcurrentHashMap使用了一种细粒度的锁机制来保证线程安全,允许多个线程同时进行读操作,而对写操作进行了分段加锁,不会阻塞其他读操作。 2. 分段锁:ConcurrentHashMap内部将数据分成多个段(Segment),每个段都维护了一个独立的哈希表。不同的线程可以同时访问不同的段,从而提高并发性能。 3. 原子性操作:ConcurrentHashMap提供了一些原子性的操作方法,如putIfAbsent()、remove()和replace()等。这些方法可以保证在操作期间没有其他线程对数据进行修改。 4. 无需加锁的读操作:ConcurrentHashMap允许多个线程同时进行并发读操作,因为读操作不会产生冲突,不需要加锁。 5. 高效性能:ConcurrentHashMap在大多数情况下具有比Hashtable和同步的HashMap更好的并发性能。 要充分理解和使用ConcurrentHashMap,你可以关注以下几点: - 理解其线程安全机制和锁的细粒度设计,避免出现死锁和竞态条件等问题。 - 确保在多线程环境下使用正确的并发控制方法,如使用原子性操作方法而不是先检查后执行的方式。 - 注意ConcurrentHashMap的迭代器可能不是强一致性的,如果需要强一致性的结果,可以考虑使用其他手段来处理。 - 根据具体的使用场景和需求,选择合适的并发度和初始容量。 希望以上信息对你有帮助!如果你还有其他问题,请继续提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值