常见的数据倾斜是怎么造成的?
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前置。
优缺点:实现简单,Spark作业性能提升,但是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上的随机数前缀,再次进行全局聚合操作。
优缺点:大幅度缓解数据倾斜,缺点是仅适用于聚合类的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都比较均匀。
实现思路:
- 算出数据量最大的那几个key,通过sample算子采样出一份样本来,然后统计一下每个key的数量。
- 将这几个key对应的数据从原来的RDD拆分出来形成一个单独的RDD,并给每个key打上n以内的随机前缀。
- 需要join的另一个RDD,也过滤出那几个倾斜key对应的数据并形成一个单独的RDD,将每条数据膨胀成n条数据,这n条数据都按照顺序附加一个0-n的前缀。
- 将附加随机前缀的RDD和膨胀n倍的独立RDD进行join,将原先相同的key打成n份,分散到多个task中去执行join。
- 而另外两个普通的RDD照常join即可。
- 最后两次join的结果使用union算子合并起来,得到最终的join结果。
- 跟方案六类似,首先查看RDD/Hive表中的数据分布情况,找到那个造成数据倾斜的RDD/Hive表,比如有多个key都对应了超过1万条数据。
- 然后将该RDD的每条数据都打上一个n以内的随机前缀。
- 同时对另外一个正常的RDD进行扩容,将每条数据都扩容成n条数据,扩容出来的每条数据都依次打上一个0-n的前缀。
- 最后将两个处理后的RDD进行join即可。