数据倾斜
现象
- client模式下看到大部分task执行完毕,小部分task仍然要执行很久
- 某些task因为数据量太大而 OOM,log中显示task failed,task lost,resubmitting task等
原因
shuffle操作时,由于相同key会被分配到同一个reduce端执行,而大部分数据的key值相同,导致部分task处理的数据量过大,分配不均
定位
- 关注会产生shuffle的算子,groupByKey、countByKey、reduceByKey、join等
- 查看log,OOM时查看报错信息;非OOM时,查看执行到的stage,通过stage定位代码
解决方案
聚合源数据
- 彻底聚合:在数据来源中(如hive etl)对数据操作,将(key,value)中的所有value用特殊数据格式放在一起(如拼接成字符串),这样数据传输到spark中时数据量大大减小,spark不用再进行聚合操作
- 粗粒度聚合:在来源中按key粗粒度聚合,如日聚合成周等
过滤/丢弃导致倾斜的key
在来源中直接舍弃该key,或者filter过滤
提高shuffle操作的reduce并行度
数据倾斜本质上是reduce端某些task处理了大量数据,可以增加reduce端task数量,分散数据,如groupByKey、countByKey、reduceByKey等算子可以传入并行度参数,表示该算子reduce端并行度
治标不治本,缓和问题
使用随机key实现双重聚合
适用于groupByKey,reduceByKey
先将key加入随机数打散,执行算子操作后再恢复原key值,最后再执行算子操作
(key,value)->(key_1,value)->groupByKey/reduceByKey->(key,value)->groupByKey/reduceByKey
reduce join转换为map join
join操作先将所有相同的key对应的values,汇聚到一个task中(shuffle),然后再进行join
当其中一个RDD的数据量相对很小时,可以将其broadcast,操作时大RDD执行map操作(mapPartition),在操作内获取broadcast的RDD,再进行数据比对,聚合等操作,这样直接避免了shuffle操作,不存在数据倾斜问题
若broadcast的RDD数据量大,则可能会导致OOM
List smallList = smallRDD.collect();
final Broadcast<List> smallBroadcast = sc.broadcast(smallList);
biggerRDD.mapToPair(
new PairFunction<Tuple2<Long,String>, String, String>() {
public Tuple2<String, String> call(Tuple2<Long, String> tuple){
List value = smallBroadcast.value();
......
}
})
采样取高频key分开join
通过采样获取高频key,将高频key抽取出来分开join,尽可能地将key分散到各个task上去进行join操作,原RDD也join表,最后将结果union
用原本会倾斜的key RDD跟其他RDD,单独去join,key对应的数据,可能就会分散到多个task中去进行join操作
此方案适用于只有少数几个高频key的情况
问题是,不是key值相同的都会放到一个task中处理吗?尽管只有一个key,即使有多个task处理,但实际上不是只会放到一个task中处理?数据倾斜依然存在
另一种方式
- 获取高频key,从两个RDD中都抽取出高频key数据,为R1,R2,剩余的为R3,R4
- 分别让R1,R2的key值添加一定范围的随机前缀
- R1 join R2(数据打散),再map剔除前缀
- R3 join R4
- 将结果union
使用随机数和扩容表进行join
当之前的数据倾斜的解决方案都失效,且两个RDD很大时使用,可以缓解数据倾斜问题
- 对一个RDD flatMap扩容,将每条数据映射为多条数据,key加上范围内随机前缀(如10)。
- 对另一个RDD map映射,每条数据打上一个10以内的随机数
- join两表(这里就意味着1/10数据join,缓解问题,没有匹配上的数据还是可以保留下来,没有剔除)
- 去除前缀
spark SQL中的数据倾斜
方法原理与前文一样
- 聚合源数据:Spark Core和Spark SQL没有任何的区别
- 过滤导致倾斜的key:在sql中用where条件
- 提高shuffle并行度:
sqlContext.setConf("spark.sql.shuffle.partitions", "1000");(默认是200) - 双重group by:使用自定义函数为关键字段添加前缀group by,再用函数去除前缀后group by
- reduce join转换为map join:修改阈值
sqlContext.setConf("spark.sql.autoBroadcastJoinThreshold", "20971520")将表做成RDD,手动去实现map join
Spark SQL内置的map join,默认是如果有一个小表,是在10M以内,默认就会将该表进行broadcast,然后执行map join;可以调节阈值 - 采样倾斜key并单独进行join:为纯Spark Core的一种方式,sample、filter等算子
- 随机key与扩容表:Spark SQL + Spark Core