Spark 从 0 到 1 学习(11) —— Spark 解决数据倾斜

1. 使用 Hive ETL 预处理数据

1.1 方案适用场景

如果导致数据倾斜的是 Hive 表。该Hive 表中的数据本身不均匀(比如某个 key 对应了 100 万数据,其他 key 才对应了 10 条数据),而且业务场景需要频繁使用 Spark 对 Hive 表执行某个分析操作,那么比较适合使用这种技术方案。

1.2 方案实现思路

此时可以评估一下,是否可以通过 Hive 来进行数据预处理(即通过 Hive ETL 预先对数据按照 key 进行聚合,或者是预先和其他表进行 join),然后在 Spark 作业中针对的数据就不是原来的 Hive 表了,而是预处理后的 Hive 表。此时由于数据以及预先进行过聚合或 join 操作了,那么在 Spark 作业中也就不需要使用原先的 shuffle 类算子执行这类操作了。

1.3 方案实现原理

这种方案从根源上解决了数据倾斜,因为彻底避免了在 Spark 中执行 shuffle 类算子,那么肯定就不会有数据倾斜的问题了。但是这里也有提醒一下大家,这种方式属于治标不治本。因为数据本身就存在分布不均匀的问题,所以 Hive ETL 中进行 group by 或 join 等 shuffle 操作时,还是会出现数据倾斜,导致 Hive ETL 的速度很慢。我们只是把数据倾斜的发生提前到了 Hive ETL 中,避免 Spark 程序发生数据倾斜而已。

2. 过滤少数导致倾斜的 key

2.1 方案适用场景

如果发现导致倾斜的 key 就少数几个,而且对计算本身的影响不大的话,那么很适合这种方案。比如 99% 的 key 就对应 10 条数据,但只有一个 key 对应了 100 万数据,从而导致了数据倾斜。

2.2 方案实现思路

如果我们判断那少数几个数据特别多的 key,对作业的执行和计算结果不是特别的重要的话,那么干脆直接过来掉那么少数几个 key。比如,在 Spark SQL 中 可以使用 where 子句过来掉这些 key 或者在 Spark Core 中对 RDD 执行 filter 算子过来掉这些 key。如果需要每次作业执行时,动态判定哪些 key 的数量最多然后在进行过滤,那么可以使用 sample 算子对 RDD 进行采样,然后计算出每个 key 的数量,取数量最多的 key 过滤掉即可。

2.3 方案实现原理

将导致数据倾斜的 key 给过滤掉之后,这些 key 就不会参与计算了,自然不可能产生数据倾斜。

3. 提高 shuffle 操作的并行度

3.1 方案实现思路

在对 RDD 执行 shuffle 算子时,给 shuffle 算子传入一个参数,比如 reduceByKey(1000),该参数就设置了这个 shuffle 算子执行时 shuffle read task 的数量。对于 Spark SQL 中的shuffle 类语句,比如 group by、join等,需要设置一个参数,即 spark.sql.shuffle.partitions,该参数代表了 shuffle read task 的并行度,该值默认是 200,对很多场景来说都有点过小。

3.2 方案实现原理

增加 shuffle read task 的数量,可以让原本分配给一个 task 的多个 key 分配给多个 task,从而让每个 task 处理比原来更少的数据。举例来说:如果原本有 5 个不同的 key,每个 key 对应10条数据,这 5个key都是分配给一个 task 的,那么这个 task 就要处理 50 条数据。而增加了 shuffle read task 以后,每个 task 就分配倒一个 key,即每个 task 就处理 10 条数据,那么自然每个 task 的执行时间都会变短了。

4. 双重聚合

4.1 方案适用场景

对 RDD 执行 reduceByKey 等聚合 shuffle 算子或者在 Spark SQL 中使用 group by 语句进行分组聚合时,比较适用这种方案。

4.2 方案实现思路

这个方案的核心实现思路就是进行两阶段聚合。

第一次是局部聚合:

先给每个 key 都打上一个 随机数,比如 10 以内的随机数,此时原先一样的 key 就变成不一样的了。比如(hello, 1) (hello, 1) (hello, 1) (hello, 1),就会变成(1_hello, 1) (1_hello, 1) (2_hello, 1) (2_hello, 1)。接着对打上随机数后的数据执行 reduceByKey 等聚合操作,进行局部聚合,那么局部聚合结果就会变成(1_hello, 2) (2_hello, 2)。

第二次是全局聚合:

然后把各个 key 的前缀去掉,就会变成(hello,2)(hello,2),再次进行全局聚合操作,就可以得到最终结果(hello, 4)。

4.3 方案实现原理

将原本相同的key 通过附加随机前缀的方式,变成多个不同的 key,就可以让原本被一个 task 处理的数据分散到多个 task 上去做局部聚合,进而解决单个 task 处理数据量过多的问题。接着除掉随机前缀,再次进行全局聚合,就可以得到最终的结果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BB9Tof6K-1602580670499)(E:\maven\big-data-cloud\spark\spark_double_groupby.png)]

如果一个 RDD 中有一个 key 导致数据清洗,同时还有其他的 key,那么一般先对数据进行抽样,然后找出倾斜的 key,再使用 filter 对原始的 RDD 进行分离出两个 RDD ,一个是由倾斜的 key 组成的 RDD1,一个是由其他的 key 组成的 RDD2,那么对于 RDD1 可以使用加随机前缀进行多分区多 task 计算,对于另一个 RDD2 正常聚合计算,最后将结果合并起来。

5. 将 reduce join 转为 map join

BroadCast + filter(或者 map)

5.1 方案适用场景

在对 RDD 使用 join 类操作,或者是在 Spark SQL 中使用 join 语句时,而且 join 操作中的一个 RDD 或者 表的数据量比较小 (比如几百 M 或者一两个 G),比较适用此方案。

5.2 方案实现思路

不使用 join 算子进行连接操作,而使用 Broadcast 变量 与 map 算子实现 join 操作,进而完全规避掉 shuffle 类的操作,彻底避免数据倾斜的发生和出现。将较小 RDD 中的数据直接通过 collect 算子拉取到 Driver 端的内存中来,然后对其创建一个 Broadcast 变量;截止对另外一个 RDD 进行 map 类算子。在算子函数内,从 Broadcast 变量中获取较小 RDD 的全量数据,与当前 RDD 的每条数据按照连接 key 进行比较,如果连接 key 相同的话,那么就将两个 RDD 的数据用你需要的方式连接起来。

5.3 方案实现原理

普通的 join 是会走 shuffle 过程的,而一旦 shuffle,就相当于会将相同 key 的数据拉取到一个 shuffle read task 中再进行 join,此时就是 reduce join。但是如果一个 RDD 是比较小的,而可以采用广播小 RDD 全量数据 + map 算子来实现与 join 同样的效果,也就是 map join,此时就不会发生 shuffle 操作,也就不会发生数据倾斜。

6. 采样倾斜 key 并分拆 join 操作

6.1 方案适用场景

两个 RDD/Hive 表进行 join 的时候,如果数据量都比较大,无法采用 “决绝方案五”,那么此时可以看下两个 RDD/Hive 表中的 key 分布情况。如果出现数据倾斜,是因为其中某一个 RDD/Hive 表中的少数据几个 key 的数据量过大,而另一个 RDD/Hive 表中的所有 key 都分布比较均匀,那么采用这个解决方案是比较合适的。

6.2 方案实现思路

对包含少数几个数据量过大的 key 的那个 RDD ,通过 sample 算子采样出来一份样本,然后统计一下每个 key 的数量,计算出数据量最大的哪几个 key。然后将这几个 key 对应的数据从原来的 RDD 中查分出来,形成一个单独的 RDD ,并给每个 key 都打上n 以内的随机数作为 前缀,而不会导致倾斜的大部分 key 形成另外一个 RDD 。接着将需要 join 的另一个 RDD ,页过滤出来那几个倾斜的 key 对应的数据并形成一个单独的 RDD,将每条数据膨胀成 n 条数据,这 n 条数据都按顺序附近一个 0~n 的前缀。不会导致倾斜的大部分 key 也形成另外一个 RDD。再讲附加了随机前缀的独立 RDD 与另一个膨胀 n 倍 的独立 RDD 进行 join,此时就可以将原先相同的 key 打散成 n 份,分散到多个 task 中去进行 join了。而另外两个普通的RDD 就照常 join 即可。最后将两次 join 的结果使用 union 算子合并起来集合,这就是最终的 join 结果。

在这里插入图片描述

7. 使用随机前缀和扩容RDD进行join

7.1 方案适用场景

如果在进行 join 操作时,RDD 中有大量的 key 导致数据倾斜,那么进行分拆 key 也没什么意义,此时就只能使用最后一种方案来解决问题了。

7.2 方案实现思路

该方案的实现思路基本和 “解决方案六” 类似,首先查看 RDD/Hive 表中的数据分布情况,找到那个造成数据倾斜的 RDD/Hive 表,比如有多个 key 都对应了超过 1万 条数据。然后将该 RDD 的每条数据都打上一个 n 以内的随机前缀。同时对另外一个正常的 RDD 进行扩容,将每条数据都扩容成 n 条数据,扩容出来的每条数据都依次打上一个 0~n 的前缀。最后将两个处理后的 RDD 进行 join 即可。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/dwjf321/article/details/109056363
今日推荐