常见的数据倾斜是怎么造成的?
Shuffle的时候,将各个节点上相同的key拉取到某个节点的一个task进行处理,比如按照key进行聚合或join等操作,如果某个key对应的数据量特别大的话,就会发生数据倾斜现象。数据倾斜就成为了整个task运行时间的短板。
触发shuffle的常见算子:distinct、groupByKey、reduceByKey、aggregateByKey、join、cogroup、repartition等。
要解决数据倾斜的问题,首先要定位数据倾斜发生在什么地方,首先是哪个stage,直接在Web UI上看就可以,然后查看运行耗时的task,查看数据是否倾斜了!
根据这个task,根据stage划分原理,推算出数据倾斜发生在哪个shuffle类算子上。
查看导致数据倾斜的key的数据分布情况
根据执行操作的不同,可以有很多种查看key分布的方式:
1,如果是Spark SQL中的group by、join语句导致的数据倾斜,那么就查询一下SQL中使用的表的key分布情况。
2,如果是Spark RDD执行shuffle算子导致的数据倾斜,那么可以在Spark作业中加入查看key分布的代码,比如RDD.countByKey()。然后对统计出来各个key出现的次数,collect、take到客户端打印一下,就可以看到key的分布情况。
比如针对wordCount案例,最后的reduceByKey算子导致了数据倾斜:
val sampledPairs = pairs.sample(false,0.1) //对pairs采样10%
val sampledWordCounts = sampledPairs.countByKey()
sampledWordCounts.foreach(println(_))
数据倾斜的解决办法
方案一:使用Hive ETL预处理数据
适用场景:导致数据倾斜的是Hive表,Hive表中的数据本身很不均匀,业务场景需要频繁使用Spark对Hive表执行某个分析操作。
实现思路:提前将join等操作执行,进行Hive阶段的ETL。将导致数据倾斜的shuffle前置。
优缺点:实现简单,Spa
rk作业性能提升,但是Hive ETL还是会发生数据倾斜,导致Hive ETL的速度很慢。
实践经验:将数据倾斜提前到上游的Hive ETL,每天就执行一次,慢就慢点吧。
方案二:过滤少数导致倾斜的key
适用场景:少数几个key导致数据倾斜,而且对计算本身影响并不大的话。
实现思路:比如Spark SQL中直接用where条件过滤掉这些key,如果是RDD的话,用filter算子过滤掉这些key。如果是动态判断哪些key的数据量最多然后再进行过滤,那么可以使用sample算子对RDD进行采样,然后计算出每个key的数量,取数据量最多的key过滤掉即可。
优缺点:实现简单,效果也好。缺点是一般情况下导致倾斜的key还是很多的,不会是少数。
解决方案三:提高shuffle操作的并行度
适用场景:直接面对数据倾斜的简单解决方案。
实现思路:对RDD执行shuffle算子时,给shuffle算子传入一个参数,比如reduceByKey(1000),该参数就设置了这个shuffle算子执行的shuffle read task的数量。对于Spark SQL中的shuffle类语句,比如group by,join等,需要设置一个参数,即spark.sql.shuffle.partitions,该参数默认值是200,对于很多场景来说有点过小。
优缺点:简单能缓解,缺点是没有根除问题,效果有限。
解决方案四:两阶段聚合(局部聚合+全局聚合)
适用场景:对RDD执行reduceByKey等聚合类shuffle算子或者在Spark SQL中使用group by语句进行分组聚合时,比较适合这种方案。
实现思路:先局部聚合,给每个key打一个小范围的随机数,比如10以内的随机数,相当于分成10份,一个task分成10个task。聚合聚合后,去掉key上的随机数前缀,再次进行全局聚合操作。
X
优缺点:大幅度缓解数据倾斜,缺点是仅适用于聚合类的shuffle操作。
解决方案五:将reduce join转为map join
适用场景:对RDD使用join类操作,或者在Spark SQL中使用join语句时,而且join操作中的一个RDD或表的数据量比较小(比如几百M或者一两G)。
实现思路:使用Broadcast广播小的RDD或小表与map类算子实现join操作,进而完全规避shuffle类的操作,彻底避免数据倾斜的发生和出现。
优缺点:根本不会发生shuffle,适用场景少,小表广播也会比较消耗内存资源。
解决方案六:采样倾斜key并分拆join操作
适用场景:两个RDD/Hive表进行join的时候,如果数据量都比较大,查看两个RDD/Hive表中的key分布情况,如果出现数据倾斜,是因为其中某一个RDD/Hive表中的少数几个key的数据量过大,而另一个RDD/Hive表中的所有key都比较均匀。
实现思路:
1,算出数据量最大的哪几个key,通过sample算子采样出一份样本来,然后统计一下每个key的数量。
2,将这几个key对应的数据从原来的RDD拆分出来形成一个单独的RDD,并给每个key打上n以内的随机前缀。
3,需要join的另一个RDD,也过滤出那几个倾斜key对应的数据并形成一个单独的RDD,将每条数据膨胀成n条数据,这n条数据都按照顺序附加一个0-n的前缀。
4,将附加随机前缀的RDD和膨胀n倍的独立RDD进行join,将原先相同的key打算成n份,分散到多个task中去执行join。
5,而另外两个普通的RDD照常join即可。
6,最后将两次join的结果使用union算子合并起来,得到最终的join结果。
优缺点:针对少数倾斜key进行扩容n倍。不适合大量的导致倾斜的key。
解决方案七:使用随机前缀和扩容RDD进行join
适用场景:进行join操作时,RDD中大量的key导致数据倾斜。
实现思路:
1,跟方案六类似,首先查看RDD/Hive表中的数据分布情况,找到那个造成数据倾斜的RDD/Hive表,比如有多个key都对应了超过1万条数据。
2,然后将该RDD的每条数据都打上一个n以内的随机前缀。
3,同时对另外一个正常的RDD进行扩容,将每条 数据都扩容成n条数据,扩容出来的每条数据都依次打上一个0-n的前缀。
4,最后将两个处理后的RDD进行join即可。
优缺点:对join类型的数据倾斜基本都可以处理,效果不错。缺点是需要对整个RDD进行扩容,对内存资源要求很高。
解决方案八:多种方案组合使用
对于较为复杂的数据倾斜场景,将多种方案组合起来使用,方案一和二,预处理一部分数据,并过滤一部分数据来缓解,其次对某些shuffle操作提升并行度,优化其性能,最后还可以针对不同的聚合或join操作,选择一种方案来优化其性能。
总结:数据倾斜,解决方案分为预处理和真实处理,预处理就是方案一中的Hive中进行ETL。真实处理都是直面导致数据倾斜的key。
首先是简单的去key方案(方案二),其次是分散key的方案三,提高并行度。
其次是分治的思想,局部聚合+全局聚合的方案四。
紧接是转移的思路,通过广播将reduce join变为map join(方案五)。
局部拆分+膨胀的思想,采样倾斜key并分拆join操作(方案六)。
全局拆分+膨胀的思想,使用随机前缀和扩容RDD进行join(方案七)。
最后是整合的思想,将上面的方案组合起来使用。