spark性能优化之数据倾斜

数据倾斜一般只会发生在shuffle过程中,针对不同的数据分布情况,可以采用以下几种方式针对不同的应用场景。

1.分析有可能发生数据倾斜(data skew)的位置及发生数据倾斜时的现象

通常会发生数据倾斜的常用方法有:distinct、groupByKey、reduceByKey、aggregateByKey、join、cogroup、repartition等等,发生数据倾斜时,可能就是你的代码中使用了这些算子中的某一个所导致的。

发生数据倾斜的现象:①个别task执行需要的时间比大多数task执行的时间多得多。②正常执行的spark作业,突然报OOM异常,这可能是自己写的业务代码所造成的,该情况少见。

2.提前对数据进行预处理

场景一:导致数据倾斜的是Hive表。如果该Hive表中的数据本身很不均匀(比如某个key对应了500万数据,其他key才对应了十几条数据),而且业务场景需要频繁使用Spark对Hive表执行某个分析操作,那么比较适合使用这种技术方案。

解决方案:通过Hive ETL预先对数据按照key进行聚合,或者是预先和其他表进行join。然后在Spark作业中针对的数据源就不是原来的Hive表了,而是预处理后的Hive表。此时由于数据已经预先进行过聚合或join操作了,那么在Spark作业中也就不需要使用原先的shuffle类算子执行这类操作了。
这种方式只是把数据倾斜发生的阶段提前到了Hive ETL阶段(T+N执行一次),仅仅避免了spark中发生数据倾斜。

3.过滤少数导致数据倾斜的key

场景二:①如果发现导致倾斜的key就少数几个,而且对计算本身的影响并不大的话,那么很适合使用这种方案。比如95%的key对应10条数据,但是只有五个key对应了100万数据,从而导致了数据倾斜。②、比如我们流量的数据,我们统计的时候,如果是出现127.0.0.XXX的ip地址时,这种数据不需要参与计算,要过滤的场景。
解决方案:①直接过滤掉那几个少数的key,当然要先确认这几个key业务是否需要。②如果需要每次作业执行时,动态判定哪些key的数据量最多然后再进行过滤,则可以使用sample算子对RDD进行采样,然后计算出每个key的数量,取数据量最多的前几个key过滤掉即可。

四、提高shuffle的并行度

解决方案:建议优先使用这种方案,因为这是处理数据倾斜最简单的一种方案。给shuffle算子传入一个参数比如reduceByKey(1000),该参数就设置了这个shuffle算子执行时shuffle read task的数量。②对于Spark SQL中的shuffle类语句,比如group by、join等,需要设置一个参数,即spark.sql.shuffle.partitions,该参数代表了shuffle read task的并行度,该值默认是200,对于很多场景来说都有点过小。

原理说明:增加shuffle read task的数量,可以让原本分配给一个task的多个key分配给多个task,从而让每个task处理比原来更少的数据。

应用注意:该方案通常无法彻底解决数据倾斜,因为如果出现一些极端情况,比如某个key对应的数据量有100万,那么无论你的task数量增加到多少,这个对应着100万数据的key肯定还是会分配到一个task中去处理,因此注定还是会发生数据倾斜的。所以这种方案只能说是在发现数据倾斜时尝试使用的第一种手段,尝试去用最简单的方法缓解数据倾斜而已,或者是和其他方案结合起来使用。

五、两阶段聚合(局部聚合+全局聚合)

应用场景:对RDD执行reduceByKey等聚合类shuffle算子或者在Spark SQL中使用group by语句进行分组聚合时,比较适用这种方案。
解决方案:第一次是局部聚合,先给每个key都打上一个随机数,比如3以内的随机数,此时原先一样的key就变成不一样的了,比如说有5个(world,1),会变成(1_world,1)、(1_world,1)(2_world,1)(1_world,1)(2_world,1)。接着对打上随机数后的数据,执行reduceByKey等聚合操作,进行局部聚合,那么局部聚合结果,就会变成了(1_world, 3) (2_world, 2)。然后将各个key的前缀给去掉,就会变成(world,3)(world,2),再次进行全局聚合操作,就可以得到最终结果了,比如(world, 5)。
总结:1、将原本相同的key通过附加随机前缀的方式,变成多个不同的key,就可以让原本被一个task处理的数据分散到多个task上去做局部聚合,进而解决单个task处理数据量过多的问题。2、接着去除掉随机前缀,再次进行全局聚合,就可以得到最终的结果。
缺点:仅仅适用于聚合类的shuffle操作,适用范围相对较窄。如果是join类的shuffle操作,还得用其他的解决方案。

六、将reduce join转为map join

应用场景:join操作中的一个RDD或表的数据量比较小(比如几百M或者一个G),比较适用此方案。
解决方案:1、不使用join算子进行连接操作,而使用Broadcast变量与map类算子实现join操作,进而完全规避掉shuffle类的操作,彻底避免数据倾斜的发生和出现。将较小RDD中的数据直接通过collect算子拉取到Driver端的内存中来,然后对其创建一个Broadcast变量;接着对另外一个RDD执行map类算子,在算子函数内,从Broadcast变量中获取较小RDD的全量数据,与当前RDD的每一条数据按照连接key进行比对,如果连接key相同的话,那么就将两个RDD的数据用你需要的方式连接起来。
2、广播变量广播出去的是bloomfitler,也就是在Spark中要实现Bloomfilter的功能
总结:1、我们知道,普通的join是会走shuffle过程的,而一旦shuffle,就相当于会将相同key的数据拉取到一个shuffle read task中再进行join,此时就是reduce join。2、但是如果一个RDD是比较小的,则可以采用广播小RDD全量数据+map算子来实现与join同样的效果,也就是map join,此时就不会发生shuffle操作,也就不会发生数据倾斜。
缺点:适用于一个大表和一个小表的情况。毕竟我们需要将小表进行广播,此时会比较消耗内存资源,driver和每个Executor内存中都会驻留一份小RDD的全量数据。如果我们广播出去的RDD数据比较大,比如10G以上,那么就可能发生内存溢出了。因此并不适合两个都是大表的情况。

七、采样倾斜key并分拆join操作

应用场景:两个RDD/Hive表进行join的时候,如果数据量都比较大,如果出现数据倾斜,是因为其中某一个RDD/Hive表中的少数几个key的数据量过大,而另一个RDD/Hive表中的所有key都分布比较均匀,那么就此采用此方案。
解决方案:

对包含少数几个数据量过大的key的那个RDD,通过sample算子采样出一份样本来,然后统计一下每个key的数量,计算出来数据量最大的是哪几个key。

②然后将这几个key对应的数据从原来的RDD中拆分出来,形成一个单独的RDD,并给每个key都打上n以内的随机数作为前缀,而不会导致倾斜的大部分key形成另外一个RDD。
③接着将需要join的另一个RDD,也过滤出来那几个倾斜key对应的数据并形成一个单独的RDD,将每条数据膨胀成n条数据,这n条数据都按顺序附加一个0~n的前缀,不会导致倾斜的大部分key也形成另外一个RDD。
④再将附加了随机前缀的独立RDD与另一个膨胀n倍的独立RDD进行join,此时就可以将原先相同的key打散成n份,分散到多个task中去进行join了。
⑤而另外两个普通的RDD就照常join即可。
⑥最后将两次join的结果使用union算子合并起来即可,就是最终的join结果。
注意:数据倾斜的rdd join之后将数据的格式保证原样性
实现原理:

①对于join导致的数据倾斜,如果只是某几个key导致了倾斜,可以将少数几个key分拆成独立RDD,并附加随机前缀打散成n份去进行join。
②此时这几个key对应的数据就不会集中在少数几个task上,而是分散到多个task进行join了。
总结:

1、对于join导致的数据倾斜,如果只是某几个key导致了倾斜,采用该方式可以用最有效的方式打散key进行join。
2、而且只需要针对少数倾斜key对应的数据进行扩容n倍,不需要对全量数据进行扩容。避免了占用过多内存。
3、如果导致倾斜的key特别多的话,比如成千上万个key都导致数据倾斜,那么这种方式也不适合。

八、使用随机前缀和扩容RDD进行join

应用场景:如果在进行join操作时,RDD中有大量的key导致数据倾斜,那么进行分拆key也没什么意义。
解决方案:与七类似,
1、首先查看RDD/Hive表中的数据分布情况,找到那个造成数据倾斜的RDD/Hive表,比如有多个key都对应了超过1万条数据。
2、然后将该RDD的每条数据都打上一个n以内的随机前缀。
3、同时对另外一个正常的RDD进行扩容,将每条数据都扩容成n条数据,扩容出来的每条数据都依次打上一个0~n的前缀。
4、最后将两个处理后的RDD进行join即可。
总结:
1、将原先一样的key通过附加随机前缀变成不一样的key,然后就可以将这些处理后的“不同key”分散到多个task中去处理,而不是让一个task处理大量的相同key。
2、该方案与“优化 ”的不同之处就在于,上一种方案是尽量只对少数倾斜key对应的数据进行特殊处理,由于处理过程需要扩容RDD,因此上一种方案扩容RDD后对内存的占用并不大;而这一种方案是针对有大量倾斜key的情况,没法将部分key拆分出来进行单独处理,因此只能对整个RDD进行数据扩容,对内存资源要求很高。
3、对join类型的数据倾斜基本都可以处理,而且效果也相对比较显著,性能提升效果非常不错。
4、缺点会占用大量的内存,因为是大量的key发生了数据倾斜。

九、多种组合方案共同使用

比如还有开发调优、资源调优、shuffle等。

你可能感兴趣的:(spark,spark,性能优化,数据倾斜)