Spark数据倾斜解决方案

2,185 阅读4分钟

数据倾斜

现象

  1. client模式下看到大部分task执行完毕,小部分task仍然要执行很久
  2. 某些task因为数据量太大而 OOM,log中显示task failed,task lost,resubmitting task等

原因

shuffle操作时,由于相同key会被分配到同一个reduce端执行,而大部分数据的key值相同,导致部分task处理的数据量过大,分配不均

定位

  1. 关注会产生shuffle的算子,groupByKey、countByKey、reduceByKey、join等
  2. 查看log,OOM时查看报错信息;非OOM时,查看执行到的stage,通过stage定位代码

解决方案

聚合源数据

  1. 彻底聚合:在数据来源中(如hive etl)对数据操作,将(key,value)中的所有value用特殊数据格式放在一起(如拼接成字符串),这样数据传输到spark中时数据量大大减小,spark不用再进行聚合操作
  2. 粗粒度聚合:在来源中按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中处理?数据倾斜依然存在

另一种方式

  1. 获取高频key,从两个RDD中都抽取出高频key数据,为R1,R2,剩余的为R3,R4
  2. 分别让R1,R2的key值添加一定范围的随机前缀
  3. R1 join R2(数据打散),再map剔除前缀
  4. R3 join R4
  5. 将结果union

使用随机数和扩容表进行join

当之前的数据倾斜的解决方案都失效,且两个RDD很大时使用,可以缓解数据倾斜问题

  1. 对一个RDD flatMap扩容,将每条数据映射为多条数据,key加上范围内随机前缀(如10)。
  2. 对另一个RDD map映射,每条数据打上一个10以内的随机数
  3. join两表(这里就意味着1/10数据join,缓解问题,没有匹配上的数据还是可以保留下来,没有剔除)
  4. 去除前缀

spark SQL中的数据倾斜

方法原理与前文一样

  1. 聚合源数据:Spark Core和Spark SQL没有任何的区别
  2. 过滤导致倾斜的key:在sql中用where条件
  3. 提高shuffle并行度:sqlContext.setConf("spark.sql.shuffle.partitions", "1000");(默认是200)
  4. 双重group by:使用自定义函数为关键字段添加前缀group by,再用函数去除前缀后group by
  5. reduce join转换为map join:修改阈值sqlContext.setConf("spark.sql.autoBroadcastJoinThreshold", "20971520") 将表做成RDD,手动去实现map join
    Spark SQL内置的map join,默认是如果有一个小表,是在10M以内,默认就会将该表进行broadcast,然后执行map join;可以调节阈值
  6. 采样倾斜key并单独进行join:为纯Spark Core的一种方式,sample、filter等算子
  7. 随机key与扩容表:Spark SQL + Spark Core

参考资料

  1. Spark性能优化指南——高级篇
  2. Spark 2.0从入门到精通