一、什么是数据倾斜
数据倾斜是大数据类型项目中最棘手的性能问题。数据倾斜一般会有两种表现:
-
大部分task执行很快,只有极少数的task执行得特别慢,可能耗费数个小时。
-
大部分task很快执行完,但有的task会突然报OOM内存溢出,导致task fail、task lost、resubmitting task,反复执行几次,特定的task还是跑不通,最后挂掉。可能是因为这个task需要处理大量的数据,创建大量的对象,内存放不下,直接爆掉。
二、原因分析
在执行shuffle操作的时候,比如说有3个task,100万数据。某一个key对应98万个values,分配到同一个task;另外两个task,每个task分配到1万个values,并且是对应不同的key。
这个时候,分别分配到1万个values的task很快执行完,但是分配到98万values的task执行起来特别慢,导致整个spark作业的运行时间特别长。
三、定位原因与出现问题的位置
出现数据倾斜,基本只可能是因为发生了shuffle操作,在shuffle的过程中,某个key对应的数据远远多于其他key对应的数据,导致数据倾斜。这种情况下,可以通过两种方式去定位:
- 到程序中去找,哪些地方用了会产生shuffle的算子,比如groupByKey、countByKey、reduceByKey、join
- 看log文件,一般会报程序的哪一行代码导致了OOM异常;或者执行到了第几个stage
下面开始介绍几个数据倾斜的解决方案:
四、聚合源数据和过滤导致数据倾斜的key
①聚合源数据
通常,在做一些聚合操作时,比如groupByKey、reduceByKey、join,拿到的源数据都是每个key对应的values,然后在spark作业中对数据进行一定的操作。
如果这些数据来自hive表,可以在hive表中对数据进行ETL,对数据进行一定的处理。由于hive比较适合做离线分析,可以做定时任务,定时对数据进行聚合(也可能是初步计算)。如果按key来分组,将key对应的所有values,用特定的方式拼接到一个字符串中去,比如“key=sessionid,value:action_seq=1|user_id=1|search_keyword=电子|category_id=01;action_seq=2|user_id=1|search_keyword=书籍|category_id=01”。
在hive表中对key进行聚合后,当需要对key进行操作时,spark中通过key拿到的数据就是hive表聚合过的数据。可能hive表进行聚合前是10万条数据,聚合后就变成了1千条数据,解决了数据倾斜的问题。
②过滤导致数据倾斜的key
对于待处理数据,可能大多数key对应的数据都是几十、几百条,只有极少数key对应的数据是数十万条。
这个时候,根据业务需求,如果允许的情况下,可以将那极少数的key对应的数据舍弃掉,避免数据倾斜。
如果上面两个方法可以采纳,那么基本上可以从源头上解决绝大部分数据倾斜问题。实在不行,再尝试下面几个方法。
五、提高shuffle操作reduce并行度
提高reduce并行度,就是提高task的数量。
举个例子,原本某个task分配数据特别多,直接OOM(内存溢出),导致程序无法运行。根据log,找到发生数据倾斜的shuffle操作,提高并行度(在算子后面传入一个参数,即可设置并行度,比如reduceByKey(xxx,1000)),让数据尽量均匀地分配到所有的task中。每个task分配到的数据量变少,避免OOM,缓解数据倾斜的问题。
注意:
这种方法并没有从根本上解决数据倾斜的问题,只是缓解了数据倾斜带来的影响。
在理想状态下,如果这步操作之后,数据倾斜的问题得到解决,最好;如果对数据倾斜问题的影响很小,则不考虑这种方法,参考其他方法。
六、使用随机key实现双重聚合
在原始key前面加一个随机数(不同场景灵活应对),生成双重key,然后进行第一轮聚合;第一轮聚合之后,再将双重key反向映射成原始key,进行第二轮聚合。
在groupByKey、reduceByKey等算子中,可以采取这个方法。
如果前面的几种方法(聚合源数据和过滤导致数据倾斜的key、提高shuffle操作reduce并行度)无法解决数据倾斜问题,则只能依靠这种方法。
七、将reduce join转换为map join
普通join,需要经过shuffle操作,是reduce join,先将所有相同key对应的values汇聚到一个task中,然后再进行join。
map join,把小的RDD做成广播变量,再进行map操作,得到聚合后的RDD,最后再进行后续操作。
①适用场景
如果两个RDD要进行join,其中一个RDD比较小(比如一个RDD是100万数据,另一个RDD是1万数据),可以采取这种方法。广播出去的RDD,在每个executor的block manager上都会有一个数据备份,所以采取这种方法的时候,要确保内存足够存放小RDD的数据。
这个方式下,不会发生shuffle操作,从根本上杜绝了数据倾斜的问题。
②不使用场景
两个RDD都比较大,如果将一个RDD广播出去,每个executor上都会有一个数据备份,可能会导致OOM(内存溢出)。
③总结
对于join这种操作,即使没有数据倾斜,在条件允许的情况下,也优先使用map join方法,避免通过shuffle进行join,牺牲少量的内存来换取更好的性能。
八、sample采样倾斜key进行两次join
将发生数据倾斜的key单独去出来,放到一个RDD中,将原始RDD拆分成两个RDD:导致倾斜的key的RDD和其他RDD。用这两个RDD单独join,此时key对应的数据会分散到多个task中进行join操作,避免数据倾斜造成的问题,然后将两次join的结果执行union操作合并起来。
①适用场景
对于join操作,优先使用map join替代普通join。如果不能采用map join,并且某一个或几个key对应的数据量比较大,考虑使用sample采样。
②不适用场景
如果一个RDD中,导致数据倾斜的key特别多,此时不建议使用sample采样的方法,建议考虑下面第九种方法(sample采样倾斜key进行两次join)。
③优化
对于采样的key,从另一个要join的表中也过滤出一份数据,比如就只有一条数据。对这一个数据,进行flatMap操作,打上100个随机数作为前缀,返回100条数据。
单独拉出来的可能产生数据倾斜的RDD,给每一个数据都带上一个100以内的随机数最为前缀。
然后在进行join,整个过程会被打散到不同的task中运行,性能会提高很多。最后,再将结果union合并。
九、使用随机数以及扩容表进行join
当前面几个方法都没法使用时,可以尝试使用随机数以及扩容表进行join。这个方法没有办法彻底解决数据倾斜,只是一种对数据倾斜的缓解。
步骤:
- 选择一个RDD,用flatMap进行扩容,将一条数据映射成多条数据,每一条映射出来的数据都到一个n以内的随机数,比如说n取10。
- 将另一个RDD,做普通的map映射,每条数据,前面带上一个n以内的随机数。
- 将两个处理后的RDD进行join操作,并将结果数据反映射,去掉前面的随机数。
局限性:
- 两个RDD都很大,所以没有办法将某一个RDD扩得特别大,一般取10。
- 扩容的倍数比较小,没法彻底解决数据倾斜的问题,只能缓解数据倾斜带来的问题。