spark数据倾斜

一 数据倾斜解决方案原理以及现象分析

1.1 数据倾斜原理

就是指某些key相关的数据和其他key的数据比例相比较,严重失衡,多太多,那么在task运行的时候每一个task需要处理的数据量就不一样,这样很容易造成,一些task很早就结束了,一些task要运行很久,拖后腿;另外提前运行完的task浪费资源。

 

1.2根据日志定位数据倾斜的位置和原因

出现数据倾斜的原因,基本是因为发生了shuffle操作,在shuffle的过程中,出现了数据倾斜的问题。因为某个,或者某些key对应的数据,远多于其他的key。

# 在自己的程序里面找找,哪些地方用了会产生shuffle的算子,groupByKey、countByKey、reduceByKey、join

# 看日志:log一般会报是在你的哪一行代码,导致了OOM异常;或者呢,看log,看看是执行到了第几个stage

 

 

二 聚合源数据以及过滤导致的倾斜的key

2.1 聚合数据源

方案一:

数据倾斜,某个key对应的100万数据,某些key对应几百条,某些key对应几十条。现在,咱们直接在生成hive表的hive ETL中,对数据进行聚合。比如按key来分组,将key对应的所有的values,全部用一种特殊的格式,拼接到一个字符串里面去,比如“key=sessionid, value:action_seq=1|user_id=1|search_keyword=火锅|category_id=001;action_seq=2|user_id=1|search_keyword=涮肉|category_id=001”。

 

对key进行group,在spark中,拿到key=sessionid,values<Iterable>;hive ETL中,直接对key进行了聚合。那么也就意味着,每个key就只对应一条数据。在spark中,就不需要再去执行groupByKey+map这种操作了。直接对每个key对应的values字符串,map操作,进行你需要的操作即可。key, values串。

方案二:

有时候可能没办法对每一条key就聚合出来一条数据,那么也可以根据不同的粒度,比如数据包含哪些城市,几天或者几个地区

 

但还是建议尽量去聚合,减少每一个key对应的数据。

2.2 过滤导致的数据倾斜

 

三 提高shuffle操作reduce并行度

将reduce task的数量,变多,就可以让每个reduce task分配到更少的数据量,这样的话,也许就可以缓解,或者甚至是基本解决掉数据倾斜的问题。一般在方案一或者二不能解决的情况下,才用这个方案

 

要给我们所有的shuffle算子,比如groupByKey、countByKey、reduceByKey。在调用的时候,传入进去一个参数。一个数字。那个数字,就代表了那个shuffle操作的reduce端的并行度。那么在进行shuffle操作的时候,就会对应着创建指定数量的reduce task。

 

这样的话,就可以让每个reduce task分配到更少的数据。基本可以缓解数据倾斜的问题。

 

比如说,原本某个task分配数据特别多,直接OOM,内存溢出了,程序没法运行,直接挂掉。按照log,找到发生数据倾斜的shuffle操作,给它传入一个并行度数字,这样的话,原先那个task分配到的数据,肯定会变少。就至少可以避免OOM的情况,程序至少是可以跑的。

这种方案是治标不治本的。因为,它没有从根本上改变数据倾斜的本质和问题。不像第一个和第二个方案(直接避免了数据倾斜的发生)

1、如果最理想的情况下,提升并行度以后,减轻了数据倾斜的问题,或者甚至可以让数据倾斜的现象忽略不计,那么就最好。就不用做其他的数据倾斜解决方案了。

 

2、不太理想的情况下,就是比如之前某个task运行特别慢,要5个小时,现在稍微快了一点,变成了4个小时;或者是原先运行到某个task,直接OOM,现在至少不会OOM了,但是那个task运行特别慢,要5个小时才能跑完。

 

四 使用随机key实现双重聚合

 

如果之前的数据存在数据倾斜,那么我们可以将原始的数据key进行映射,比如如上图所示,生成随机的数字,然后就可以把key弄的均匀。然后在进行key的还原。一般适用于groupByKey & reduceByKey。

第一轮聚合的时候,对key进行打散,将原先一样的key,变成不一样的key,相当于是将每个key分为多组;

先针对多个组,进行key的局部聚合;接着,再去除掉每个key的前缀,然后对所有的key,进行全局的聚合。

对groupByKey、reduceByKey造成的数据倾斜,有比较好的效果。

//         /**

//         * 第一步,给每个key打上一个随机数

//         */

//         JavaPairRDD<String,Long> mappedClickCategoryIdRDD = clickCategoryIdRDD.mapToPair(

//                   

//                    newPairFunction<Tuple2<Long,Long>, String, Long>() {

//

//                          privatestatic final long serialVersionUID = 1L;

//        

//                          @Override

//                          publicTuple2<String, Long> call(Tuple2<Long, Long> tuple)

//                                     throwsException {

//                                Randomrandom = new Random();

//                                intprefix = random.nextInt(10);

//                                returnnew Tuple2<String, Long>(prefix + "_" + tuple._1, tuple._2);

//                          }

//                         

//                    });

//        

//         /**

//         * 第二步,执行第一轮局部聚合

//         */

//         JavaPairRDD<String,Long> firstAggrRDD = mappedClickCategoryIdRDD.reduceByKey(

//                   

//                    newFunction2<Long, Long, Long>() {

//

//                          privatestatic final long serialVersionUID = 1L;

//

//                          @Override

//                          publicLong call(Long v1, Long v2) throws Exception {

//                                returnv1 + v2;

//                          }

//                         

//                    });

//        

//         /**

//         * 第三步,去除掉每个key的前缀

//         */

//         JavaPairRDD<Long,Long> restoredRDD = firstAggrRDD.mapToPair(

//                   

//                    newPairFunction<Tuple2<String,Long>, Long, Long>() {

//

//                          privatestatic final long serialVersionUID = 1L;

//        

//                          @Override

//                          publicTuple2<Long, Long> call(Tuple2<String, Long> tuple)

//                                     throwsException {

//                                longcategoryId = Long.valueOf(tuple._1.split("_")[1]); 

//                                returnnew Tuple2<Long, Long>(categoryId, tuple._2); 

//                          }

//                         

//                    });

//        

//         /**

//         * 第四步,最第二轮全局的聚合

//         */

//         JavaPairRDD<Long,Long> clickCategoryId2CountRDD = restoredRDD.reduceByKey(

//                   

//                    newFunction2<Long, Long, Long>() {

//

//                          privatestatic final long serialVersionUID = 1L;

//

//                          @Override

//                          publicLong call(Long v1, Long v2) throws Exception {

//                                returnv1 + v2;

//                          }

//                         

//                    });

五 将Reduce Join 转换为Map Join

 

使用场景:

如果两个RDD要进行join,其中一个RDD是比较小的。一个RDD是100万数据,一个RDD是1万数据。(一个RDD是1亿数据,一个RDD是100万数据)。

 

其中一个RDD必须是比较小的,broadcast出去那个小RDD的数据以后,就会在每个executor的block manager中都有一份。要确保你的内存足够存放那个小RDD中的数据。

 

这种方式下,根本不会发生shuffle操作,肯定也不会发生数据倾斜;从根本上杜绝了join操作可能导致的数据倾斜的问题

 

对于join中有数据倾斜的情况,大家尽量第一时间先考虑这种方式,效果非常好;如果某个RDD比较小的情况下。

 

注意:

两个RDD都比较大,那么这个时候,你去将其中一个RDD做成broadcast,就很笨拙了。很可能导致内存不足。最终导致内存溢出,程序挂掉。

 

 

 

 

六 Sample采样倾斜key进行两次join

核心思想:

就是对数据进行一定比例的抽样,然后对key进行统计排序,决定哪些key有可能会发生数据倾斜,一般排在越前面的越容易发生数据倾斜。

将获取的的这些可能发生倾斜的key和普通表的key分别和需要join的数据进行join操作,然后将两个结果合并。

 

使用场景:

如果要join的两个RDD数据都很大,reduce join 专户为 map join的方案就不太好用了,因为需要广播变量,对于内存要求很高。

 

针对你的RDD的数据,此时如果你发现整个RDD就一个,或者少数几个key,是对应的数据量特别多,可以使用该方案。 但是如果导致数据倾斜的key,特别多,该方案就不太适合了。

/** sample采样倾斜key单独进行join */
JavaPairRDD<Long,String>sampledRdd = userId2PartAggrInfoRdd.sample(false,0.1, 9);
JavaPairRDD<Long,Long>mappedSampledRdd = sampledRdd.mapToPair(
    new PairFunction<Tuple2<Long,String>, Long,Long>() {
        public Tuple2<Long,Long> call(Tuple2<Long,String> tuple)throws Exception {
            return new Tuple2<Long,Long>(tuple._1(),1L);
        }
    });
JavaPairRDD<Long,Long>computedSampledRdd = mappedSampledRdd.reduceByKey(
    new Function2<Long,Long, Long>() {
        public Longcall(Long v1, Long v2) throws Exception {
            return v1+ v2;
        }
    });
// 将结果反转,便于排序
JavaPairRDD<Long,Long>reversedSampledRdd = computedSampledRdd.mapToPair(
    new PairFunction<Tuple2<Long,Long>, Long,Long>() {
        public Tuple2<Long,Long> call(Tuple2<Long,Long> tuple)throws Exception {
            return new Tuple2<Long,Long>(tuple._2(),tuple._1());
        }
    });
// 取出样本中,数据最多的key,即最有可能发生数据倾斜的key
final LongskewedUserId = reversedSampledRdd.sortByKey(Boolean.FALSE).take(1).get(0)._2();
// 这个filter用于过滤出产生数据倾斜的key,然后生成一个新的Rdd
JavaPairRDD<Long,String>skewedRdd = userId2PartAggrInfoRdd.filter(newFunction<Tuple2<Long,String>, Boolean>() {
    public Booleancall(Tuple2<Long,String> tuple)throws Exception {
        return tuple._1() ==skewedUserId;
    }
});
// 这个filter用于过滤出不产生数据倾斜的key,然后生成一个新的Rdd
JavaPairRDD<Long,String>commonRdd = userId2PartAggrInfoRdd.filter(newFunction<Tuple2<Long,String>, Boolean>() {
    public Booleancall(Tuple2<Long,String> tuple)throws Exception {
        return tuple._1() !=skewedUserId;
    }
});
JavaPairRDD<Long,Tuple2<String,Row>>joinedSkewedRdd = skewedRdd.join(userId2InfoRdd);
JavaPairRDD<Long,Tuple2<String,Row>>joinedCommonRdd = commonRdd.join(userId2InfoRdd);
JavaPairRDD<Long,Tuple2<String,Row>>unionRdd = joinedSkewedRdd.union(joinedCommonRdd);

 

 

七 使用随机数以及扩容表进行Join

当采用随机数和扩容表进行join解决数据倾斜的时候,就代表着,你的之前的数据倾斜的解决方案,都没法使用。

 

这个方案是没办法彻底解决数据倾斜的,更多的,是一种对数据倾斜的缓解。

 

核心思想:

# 选择一个RDD,用flatMap进行扩容,将每一条数据映射为多条数据,每一个映射出来的数据,都带了一个N以内的随机数。通常来说,会选择10

 

# 将另外一个RDD,做普通的map映射操作,每条数据,都打上一个10以内的随机数。

# 最后,将两个处理后的RDD,进行join操作

缺点:

1、因为你的两个RDD都很大,所以你没有办法去将某一个RDD扩的特别大,一般咱们就是10倍。

2、如果就是10倍的话,那么数据倾斜问题,的确是只能说是缓解和减轻,不能说彻底解决。

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫言静好、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值