继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Spark 数据倾斜调优

损失函数
关注TA
已关注
手记 70
粉丝 1533
获赞 2735

一、what is a shuffle?

1.1 shuffle简介

一个stage执行完后,下一个stage开始执行的每个task会从上一个stage执行的task所在的节点,通过网络传输获取task需要处理的所有key,然后每个task对相同的key进行算子操作,这个过程就是shuffle过程

我们常说的shuffle过程之所以慢是因为有大量的磁盘IO以及网络传输操作。spark中负责shuffle的组件主要是ShuffleManager,在spark1.1之前采用的都是HashShuffleManager,在1.1之后开始引入效果更优SortShuffleManager,并在1.2开始默认使用SortShuffleManager。

1.2 HashShuffleManager

我们来看下最初的ShuffleManager:HashShuffleManager中shuffle的读写过程

图片描述

从上图我们可以看出,Executor中每个core对应的task在shuffle写的时候都会产生和下一个stage包含task数目一样的磁盘文件,也就是说下一个stage包含多少个task(即Reducer),当前stage的task就会产生多少个磁盘文件。那么100个单核的Executor,当前stage有200个task,每个Executor负责执行2个task,下一个stage有100个task,那么一次shuffle write需要产生200*100=20000个磁盘文件。每个buffer(即图中的bucket)的大小默认为32KB(Spark1.1中默认是100KB,可以通过spark.shuffle.file.buffer.kb来设置);在shuffle read阶段每个task从上一个stage中的每一个task中通过网络传输拉取相同key的数据进行聚合等shuffle操作。所以产生的磁盘文件越多,shuffle read的IO操作就越频繁,且大量的buffer将对Executor的存储空间产生巨大的压力。

Spark团队对针对“磁盘文件多”这一弊端进行了优化,优化后的HashShuffleManager的shuffle的读写过程:

图片描述

从上图我们可以看出,下一个stage的每个task的入度变成了优化前的一半,主要是因为每个core都产生了和下一个stage的task相同数目的磁盘文件,同一core中的不同task复用一批磁盘文件,减少磁盘文件数据,提升shuffle write性能。那么与上面相同环境下,优化后需要产生的磁盘文件数量为Executor数*Executor的core数*下一个stage的task数=100*1*100=10000。可以通过将spark.shuffle.consolidateFiles设置为true来开启consolidate机制,即优化后的HashShuffleManager。

1.3 sortShuffleManager

Spark 1.2 后开始默认使用sortShuffleManager

图片描述

SortShuffleManager主要改进点是在内存溢写到磁盘文件之前,会根据Partition id以及key对内存数据进行sort排序,然后再分批写入磁盘文件,分批的batch数量大小为1w条,最后将产生的多个磁盘文件merge成一个磁盘文件,并产生一个索引文件,用以标识下游stage中的各个task的数据在文件中的start offset 和 end offset,直观来看,一个task仅产生一个磁盘文件和一个索引文件。产生的磁盘文件少了,但增加了排序的性能开销,如果这部分在你的业务场景下开销大,那么可以选择SortShuffleManager的bypass机制。

在ShuffleManager一路优化的过程中,一个重要优化思想其实就是在减少shuffle过程中产生磁盘文件数量,一个直观的逻辑:磁盘文件少,上下游stage需要进行的磁盘IO操作就相对少了。而磁盘文件过多会带来以下问题:

  • 如果磁盘文件多,进行shuffle操作时需要同时打开的文件数多,大量的文件句柄和写操作分配的临时内存将对内存和GC带来压力,特别是在YARN的模式下,往往Executor分配的内存不足以支持这么大的内存压力;
  • 如果磁盘文件多,那么其所带来的随机读写需要多次磁盘寻道和旋转延迟,比顺序读写的时间多许多倍。

可以通过Spark.shuffle.manager参数来设置使用哪种shuffle manager。

以上我们介绍了what is a shuffle,shuffle write 与 shuffle read的过程,以及为什么shuffle对spark任务性能消耗大,在整体上了解shuffle之后,我们来了解下如何handle shuffle。

二、判断&定位

spark web ui 上task的执行时间或分配的数据量,如果一般task执行时间只有几秒,而某些task执行时间是几分钟甚至更久,那这部分task对于的stage就出现了数据倾斜,根据之前的stage的划分方式即可定位哪段代码中的算子导致了数据倾斜。

常见的触发shuffle操作的算子:distinct、groupByKey、reduceByKey、aggregateByKey、join、cogroup、repartition等

三、深究key分布

  1. 如果是数据倾斜的数据来源于hive表,那么我们可以分析下spark sql中key的数据分布情况
  2. 如果数据来源于中间的RDD,那么可以使用RDD.countByKey()来统计不同key出现的次数

如果数据量大,可以使用采样来分析,比如:

val sampledRDD = shuffleRDD.sample(false, 0.1)
val sampledKeyCounts = sampledRDD.countByKey()
sampledKeyCounts.foreach(println(_))

四、How to fix it?

  1. 数据来源于hive表,将导致数据倾斜的shuffle算子前置到**hive ETL(提取、转换和加载)**中,之后的spark任务可反复基于hive ETL后的中间表,保证了spark任务的性能。适用于多次数据计算,且对spark性能要求高的场景。
  2. 不是所有的数据都有用,如果filter少数几个数据量大的key不影响数据结果,那在数据预处理的时候可以进行过滤,或者需要动态判定key是否有用,可以在数据计算前对RDD进行sample采样,过滤数据量大的key,这样不仅可以避免数据倾斜,也可以避免相同的代码在某天突然OOM的情况,有可能这一天有某个平时表现正常的key暴增导致OOM。
  3. shuffle算子并行操作,我们知道在shuffle过程中,分布在不同task的相同key的数据会通过网络传输到同一个task进行shuffle计算,这时候一个task可能会处理多种key的数据,比如k1,k2,k3可能都被拉取到某一个task上进行reduce操作,如果k1,k2,k3的数量比较大,我们可以通过提高reduce的并行度来使得k1,k2,k3的数据分别拉取到t1,t2,t3三个task上计算,怎么做呢?如果是RDD的shuffle操作,给shuffle算子传入一个参数即可,比如reduceByKey(600),如果是Spark SQL的shuffle操作,配置一个shuffle参数:spark.sql.shuffle.partitions,该参数表示shuffle read task的并行度,默认200,可根据业务场景进行修改。
  4. key 散列设计再聚合,spark的shuffle操作导致的数据倾斜问题在一定意义上可以类比HBase的热点问题,因此HBase的rowkey的散列设计思想可以套用在聚合类的shuffle操作导致的数据倾斜的场景,怎么做呢?先对key进行hash散列,可以使用随机数,也可以针对key的具体内容进行hash,目的是将原本数据量大的key先hash成k个的key,那么原本必须拉取到一个task上进行shuffle计算的数据可以拉取到k个不同的task上计算,在一定程度上可以缓解单个task处理过多数据导致的数据倾斜,然后再对局部聚合后的key去除hash再聚合。这种key散列设计思想在解决join的shuffle操作广泛使用
  5. ”map join" replace “reduce join”,如果join操作是大小表的join,可以考虑将小表广播,首先collect到driver的内存中,为其创建一个broadcase变量,这时候Driver和每个Executor都会保存一份小表的全量数据,再在map操作中自定义join的逻辑,在这个join逻辑里,使用已在内存中的全量的小表数据与大表的每一条数据进行key对比&连接,用map join来代替普通的reduce join,可以避免数据倾斜。由于需要在内存中存放全量小表,所以小表数据量在一两G是可取的。
打开App,阅读手记
1人推荐
发表评论
随时随地看视频慕课网APP

热门评论

www.baidu.com

查看全部评论