1、在项目中有遇到过数据倾斜吗?
首先要知道什么是数据倾斜。我们知道,在执行shuffle操作的时候,相同的key对应的value一定会被分配到同一个reducetask中去处理,所以当一批数据中相同key的数据过多,就会导致一个reducetask迟迟跑不完。现在我们来定义一下数据倾斜的概念:一批数据中相同key的数据过多而导致其他reducetask跑完,而一个reducetask迟迟跑不完,甚至触发OOM的现象,称为数据倾斜。
在面试的时候,就算没有遇到过这个问题,我们也要回答遇到过,因为数据倾斜是大数据中非常常见的问题,如果你回答没有遇到过,那面试官可能会觉得你经验不够丰富,另一方面,很明显面试官想考考你这方面的知识,你回答没有,那不就把话题聊死了!那还这么面?所以我们必须要回答遇到过,然后用下面的知识在面试官面前装逼。
2、你在项目中的如何解决这个问题的?
2.1 定位发生数据倾斜的位置
如果发生数据倾斜,99.9%的可能性是因为shuffle操作将大量相同key的数据分配到同一个reducetask中去处理了(剩下0.1%看天)。所以我们首先要定位发生数据倾斜的位置。刚刚说了,发生数据倾斜是因为产生了shuffle操作,所以第一步就是找产生shuffle的RDD,要定位数据倾斜的位置,当然是去查log了,或者打开UI界面,看看是哪一个stage产生很慢,这个stage前面的ShuffledRDD,就是产生数据倾斜的罪魁祸首了。
2.2 对症下药
虽然产生数据倾斜的原因大致是一样的,但是也有些差别,同样处理方式也有些差别,下面我将逐个介绍在哪些地方可以处理数据倾斜。
2.2.1 源数据发生数据倾斜
Spark作业的来源通常是经过Hive ETL后放在HDFS中的数据,这里就可能会造成数据倾斜的问题,比如一个key对应10w条数据,而另一个key对应了100w条数据,那你跑spark任务的时候,肯定就会产生数据倾斜了。
解决办法
(1) ETL端做聚合
首先,针对这种情况,我们一般可以通过Hive ETL预先对数据按照key进行聚合,或者是预先和其他表进行join,然后在Spark作业中针对的数据源就不是原来的Hive表了,而是预处理后的Hive表。此时由于数据已经预先进行过聚合或join操作了,那么在Spark作业中也就不需要使用原先的shuffle类算子执行这类操作了。
但是,Hive ETL中进行group by或者join等shuffle操作时,还是会出现数据倾斜,导致Hive ETL的速度很慢。我们只是把数据倾斜的发生提前到了Hive ETL中。
(2) 对key做粒度细化
其次,我们可以选择对key做粒度细化,比如key原先是城市-人口,每一个key都对应着1000w人,现在我们将key进行粒度细化,使它变成县城-人口,这样每个key就对应着100w人,这也是一个解决办法。
(3)过滤导致数据倾斜的key
直接过滤掉那些引起倾斜的Key。这种方法很简单,既然你倾斜,那我不用你就完事。比如说,总共有100万个key。只有2个key,是数据量达到10 万的。其他所有的key,对应的数量都是几十,这样join后会引起倾斜。这个时候,自己可以去取舍,如果业务和需求可以理解和接受的话,在从hive 表查询源数据的时候,直接在sql中用where条件,过滤掉某几个key 。那么这几个原先有大量数据,会导致数据倾斜的key,被过滤掉之后,那么在的spark作业中,自然就不会发生数据倾斜了。
2.2.2 shuffle过程发生数据倾斜
如果是因为使用了shuffledRDD(如groupBykey、reduceBykey)导致了数据倾斜则可以使用下面的解决办法。
解决办法
(1)简单粗暴但有效—提高shuffle的操作并行度
在对RDD执行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 处理比原来更少的数据 。举例来说, 如果原本有5个key,每个key对应10条数据,这5个key都是分配给一个task的,那么这个task就要处理50条数据。而增加了shuffle read task以后, 每个task就分配到一个key,即每个task就处理10条数据,那么自然每个task的执行时间都会变短了。
但是,这种方法治标不治本,如果可以解决数据倾斜的问题,我们可以考虑使用这种方法。
(2) 对key进行加盐操作
所谓加盐,就是对key的前缀加随机值的操作,第一轮聚合的时候,对key进行打散,将原先一样的key,变成不一样的key,相当于是将每个key分为多组。先针对多个组,进行key的局部聚合。接着,再去除掉每个key的前缀,然后对所有的key进行全局的聚合。
2.2.3 大小表join 发生数据倾斜
当两个RDD要执行join操作的时候,它们肯定是要进行shuffle的,但是如果一个RDD数据量很大,而另一个RDD数据量很小,那么这样也会产生数据倾斜的问题。
解决办法
通常针对这种情况,我们的解决办法就是让join发生在map端(和mapreduce一样),直接让小的RDD变成一个广播变量,让每个excutor都保存一份进行join。这样就避免了shuffle操作。
值得注意的是,对于join这种操作,不光是考虑数据倾斜的问题。即使是没有数据倾斜问题,也完全可以优先考虑,用我们讲的这种高级的reduce join转map join的技术,不要用普通的join,去通过shuffle,进行数据的join。完全可以通过简单的map,使用map join的方式,牺牲一点内存资源。在可行的情况下,优先这么使用。
但是针对两个RDD都很大的情况下,这种情况就不适用了,很可能会造成OOM。
2.2.4 某一个key对应大量数据
之前说过,某一个key对应大量数据时,我们可以根据需要,直接在ETL时就使用where子句将导致数据倾斜的key过滤掉,但有时候我们不得不处理它,这就产生了一个问题:该怎么处理这个key呢?
解决办法
单拉出来那个最多的key,单独进行join,尽可能地将key分散到各个task上去进行join操作。
3、总结
这里要说明一下,上述解决方案是按适用性来进行列举的,也就是说,第一个,在数据源端进行聚合是最常用的解决方案,如果是一个方案不适用,才会使用下一个方案,其次,我们在面试过程中,要有条理地阐述各个解决方案,你可以说其中一种场景,使用了什么样的办法,解决了这个问题,不能一上来就把底全给交了,那面试官会觉得你在被答案,还是要假装思索一波的。