一 数据倾斜解决方案原理以及现象分析
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倍的话,那么数据倾斜问题,的确是只能说是缓解和减轻,不能说彻底解决。