spark调优--数据倾斜调优

目录:

一 、数据倾斜介绍与定位

二、解决方法一:聚合数据源

三、解决方法二:提高 shuffle 操作 reduce 并行度

四、解决方法三:将 reduce join 转换为 map join

五 、解决方法四:sample 采样倾斜 key 进行两次 join

六、解决方法之五:使用随机数以及扩容表进行 join

 

一 、数据倾斜介绍与定位

a 、数据倾斜的原理

  在执行 shuffle 操作的时候,大家都知道,我们之前讲解过 shuffle 的原理。是按照 key,来进行 values 的数据的输出、拉取和聚合的。同一个 key 的 values,一定是分配到一个 reduce task 进行处理的。多个 key 对应的 values,总共是 90 万。但是问题是,可能某个 key 对应了 88 万数据,key-88 万 values,分配到一个 task 上去面去执行。另外两个 task,可能各分配到了 1 万数据,可能是数百个 key,对应的 1 万条数据。第一个和第二个 task,各分配到了 1 万数据;那么可能 1 万条数据,需要 10 分钟计算完毕;第一个和第二个 task,可能同时在 10 分钟内都运行完了;第三个 task 要 88 万条,88* 10 = 880 分钟 = 14.5 个小时;

b 、数据倾斜的现象,有两种表现 :

1 、大部分的 task,都执行的特别特别快,刷刷刷,就执行完了(你要用 client 模式,standalone client,yarn client,本地机器主要一执行 spark-submit 脚本,就会开始打印 log),task175 finished;剩下几个 task,执行的特别特别慢,前面的 task,一般 1s 可以执行完 5 个;最后发现 1000 个 task,998,999 task,要执行 1 个小时,2 个小时才能执行完一个 task。出现数据倾斜了还算好的,因为虽然老牛拉破车一样,非常慢,但是至少还能跑。

2 、运行的时候,其他 task 都刷刷刷执行完了,也没什么特别的问题;但是有的 task,就是会突然间,啪,报了一个 OOM,JVM Out Of Memory,内存溢出了,task failed,task lost,resubmitting task。反复执行几次都到了某个 task 就是跑不通,最后就挂掉。某个 task 就直接 OOM,那么基本上也是因为数据倾斜了,task 分配的数量实在是太大了!!!所以内存放不下,然后你的 task 每处理一条数据,还要创建大量的对象。内存爆掉了。出现数据倾斜了这种就不太好了,因为你的程序如果不去解决数据倾斜的问题,压根儿就跑不出来。

c 、数据倾斜定位与出现问题的位置:

根据 log 去定位出现数据倾斜的原因,基本 只可能是因为发生了 shuffle 操作,在 shuffle 的过程中,出现了数据倾斜的问题。因为某个,或者某些 key 对应的数据,远远的高于其他的 key。

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

2、 看看 log 

  log 一般会报是在你的哪一行代码,导致了 OOM 异常;或者呢,看 log,看看是执行到了第几个 stage!!!哪一个 stage,task 特别慢,就能够自己用肉眼去对你的 spark 代码进行stage 的划分,就能够通过 stage 定位到你的代码,哪里发生了数据倾斜。去找找,代码那个地方,是哪个 shuffle 操作。

二、解决方法一:聚合数据源

聚合数据源做法一:

  groupByKey、reduceByKey;groupByKey,就是拿到每个 key 对应的 values;reduceByKey,说白了,就是对每个 key 对应的 values 执行一定的计算。现在这些操作,比如 groupByKey和 reduceByKey,包括之前说的 join。都是在 spark 作业中执行的。

  spark 作业的数据来源,通常是哪里呢?90%的情况下,数据来源都是 hive 表(hdfs,大数据分布式存储系统)。hdfs 上存储的大数据。hive 表,hive 表中的数据,通常是怎么出来的呢?有了 spark 以后,hive 比较适合做什么事情?hive 就是适合做离线的,晚上凌晨跑的,ETL(extract transform load,数据的采集、清洗、导入),hive sql,去做这些事情,从而去形成一个完整的 hive 中的数据仓库;说白了,数据仓库,就是一堆表。spark 作业的源表,hive 表,其实通常情况下来说,也是通过某些 hive etl 生成的。hive etl 可能是晚上凌晨在那儿跑。今天跑昨天的数据。

  数据倾斜,某个 key 对应的 80 万数据,某些 key 对应几百条,某些 key 对应几十条;现在,咱们直接在生成 hive 表的 hive etl 中,对数据进行聚合。比如按 key 来分组,将 key对应的所有的 values,全部用一种特殊的格式,拼接到一个字符串里面去,比如“key=sessionid,value: action_seq=1|user_id=1|search_keyword….”。对 key 进行 group,在 spark 中,拿到 key=sessionid,values<Iterable>;hive etl 中,直接对 key 进行了聚合。那么也就意味着,每个 key 就只对应一条数据。在 在 spark 中,就不需行 要再去执行 groupByKey+map 这种操作了。直接对每个 key 对应的 values 字符串,map 操作,进行你需要的操作即可。key,values 串。spark 中,可能对这个操作,就 不需要执行 shffule操作了,也就根本不可能导致数据倾斜。或者是,对每个 key 在 hive etl 中进行聚合,对所有 values 聚合一下,不一定是拼接起来,可能是直接进行计算。reduceByKey,计算函数,应用在 hive etl 中,每个 key 的 values。

聚合数据源做法二:

  你可能没有办法对每个 key,就聚合出来一条数据;那么也可以做一个妥协;对每个 key 对应的数据,10 万条;有好几个粒度,比如 10 万条里面包含了几个城市、几天、几个地区的数据, 现在放粗粒度;直接就按照城市粒度,做一下聚合,几个城市,几天、几个地区粒度的数据,都给聚合起来。比如说city_id,date,area_id ,select ... from ... group by city_id,date,area_id。尽量去聚合,减少每个 key 对应的数量,也许聚合到比较粗的粒度之后,原先有 10 万数据量的 key,现在只有 1 万数据量。

三 、解决方法二:提高 shuffle 操作 reduce 并行度

  如果第一种方法不适合做。那么采用第二种方法:高 提高 shuffle 操作的 reduce 并行度将加 增加 reduce task 的数量,就可以让每个 reduce task 分配到更少的数据量,这样的话,也许就可以缓解,或者甚至是基本解决掉数据倾斜的问题。

a 、原理图介绍:

b 、提升 shuffle reduce 端并行度的具体操作

  主要给我们所有的 shuffle 算子,比如 groupByKey、countByKey、reduceByKey。 在调用的时候,传入进去一个参数。一个数字。那个数字,就个 代表了那个 shuffle 操作的 reduce端的并行度。那么在进行 shuffle 操作的时候,就会对应着创建指定数量的 reduce task。这样的话,就可以让每个 reduce task 分配到更少的数据。基本可以缓解数据倾斜的问题。比如说,原本某个 task 分配数据特别多,直接 OOM,内存溢出了,程序没法运行,直接挂掉。按照 log,找到发生数据倾斜的 shuffle 操作,给它传入一个并行度数字,这样的话,原先那个 task 分配到的数据,肯定会变少。就至少可以避免 OOM 的情况,程序至少是可以跑的。

c 、提升 shuffle reduce 并行度的缺陷

  治标不治本的意思,因为, 它没有从根本上改变数据倾斜的本质和问题。不像第一个和第二个方案(直接避免了数据倾斜的发生)。原理没有改变,只是说,尽可能地去缓解和减轻 shuffle reduce task 的数据压力,以及数据倾斜的问题。

实际生产环境中的经验:

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

2 、不太理想的情况下,就是比如之前某个 task 运行特别慢,要 5 个小时,现在稍微快了一点,变成了 4 个小时;或者是原先运行到某个 task,直接 OOM,现在至少不会 OOM 了,但是那个 task 运行特别慢,要 5 个小时才能跑完。那么,如果出现第二种情况的话,各位,就立即放弃这种方法,开始去尝试和选择后面的方法解决。

四、解决方法之三:将 reduce join 转换为 map join

普通 reduce join :                                                                                          map join:

                    

普通的 join

肯定是要走 shuffle;那么,所以既然是走 shuffle,那么普通的 join,就肯定是走的是 reducejoin。先将所有相同的 key,对应的 values,汇聚到一个 task 中,然后再进行 join。

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,就很笨拙了。很可能导致内存不足。最终导致内存溢出,程序挂掉。而且其中某些 key(或者是某个

key),还发生了数据倾斜;此时可以采用最后两种方式。

特别声明:对于 join 这种操作,不光是考虑数据倾斜的问题; 即使是没有数据倾斜问题,也完全的 可以优先考虑,用我们讲的这种高级的 reduce join 转 转 map join 的技术,不要用普通的 join,去通过 shuffle,进行数据的 join;完全可以通过简单的 map,使用 map join 的方式,牺牲一点内存资源;在可行的情况下,优先这么使用。走 不走 shuffle ,直接走 map,性能肯定是提高很多的。

 

5 、解决方法之四:sample 采样倾斜 key 进行两次 join

方案的实现思路:

  其实关键之处在于,将发生数据倾斜的 key,单独拉出来,放到一个 RDD中去;就用这个原本会倾斜的 key RDD 跟其他 RDD,单独去 join 一下,这个时候,key 对应的数据,可能就会分散到多个 task 中去进行 join 操作,最后将 join 后的表进行 union 操作。就不至于,这个 key 跟之前其他的 key 混合在一个 RDD 中时,导致一个 key 对应的所有数据,都到一个 task 中去,就会导致数据倾斜。

应用场景:

  优先对于 join,肯定是希望能够采用上一讲讲的,reduce join 转换 map join。 两个 RDD 数 数据都比较 大,那么就不要那么搞了。针对你的 RDD 的数据,你可以自己把它转换成一个中间表,或者是用 直接用 countByKey()个 的方式,你可以看一下这个 RDD 各个 key 对应的数据量;此时如果你发现整个 RDD 就一个,或者少数几个 key,是对应的数据量特别多;尽量建议,比如就是一个 key 对应的数据量特别多。此时可以采用咱们的这种方案,单拉出来那个最多的 key;单独进行 join,尽可能地将 key分散到各个 task 上去进行 join 操作。

什么时候不适用呢?

  如果一个 RDD 中,导致数据倾斜的 key ,特别多;那么此时,最好还是不要这样了;还是使用我们最后一个方案,终极的 join 数据倾斜的解决方案。

进一步优化:

  就是说,咱们单拉出来了,一个或者少数几个可能会产生数据倾斜的 key,然后还可以进行更加优化的一个操作;对于那个 key,从另外一个要 join 的表中,也过滤出来一份数据,比如可能就只有一条数据。userid2infoRDD,一个 userid key,就对应一条数据。然后呢,采取对那个只有一条数据的 RDD,进行 flatMap 操作,打上 100 个随机数,作为前缀,返回 100 条数据。单独拉出来的可能产生数据倾斜的 RDD,给每一条数据,都打上一个 100 以内的随机数,作为前缀。再去进行 join,是不是性能就更好了。肯定可以将数据进行打散,去进行 join。join 完以后,可以执行 map 操作,去将之前打上的随机数,给去掉,然后再和另外一个普通 RDDjoin 以后的结果,进行 union 操作。

 

6 、解决方法之五:使用随机数以及扩容表进行 join

当采用随机数和扩容表进行 join 解决数据倾斜的时候,就代表着,你的之前的数据倾斜的解决方案,都没法使用。这个方案是 没办法彻底解决数据倾斜的,更多的,是一种对数据倾斜的缓解。

步骤 :

1、选择一个 RDD,要用 flatMap,进行扩容( 比较小的 RDD),将每条数据,映射为多条数据,每个映射出来的数据,都带了一个 n 以内的随机数,通常来说,会选择 10 以内。

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

3、最后,将两个处理后的 RDD,进行 join 操作。

另一个方法:

sample 采样倾斜 key 并单独进行 join

1、将 key,从另外一个 RDD 中过滤出的数据,可能只有一条,或者几条,此时,咱们可以任意进行扩容,扩成 1000 倍。

2、将从第一个 RDD 中拆分出来的那个 倾斜 key RDD, 打上 1000 以内的一个随机数。

3、join 并且提供并行度。这样配合上,提升 shuffle reduce 并行度,join(rdd, 1000)。通常情况下,效果还是非常不错的。打散成 100 份,甚至 1000 份,2000 份,去进行 join,那么就肯定没有数据倾斜的问题了吧。

此方法局限性:

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

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

 

posted @   guoyu1  阅读(107)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示