Spark性能优化指南(高级篇)
- Spark性能优化指南(高级篇)
- 数据倾斜调优
- 调优概述
- 数据倾斜发生时的现象
- 数据倾斜发生的原理
- 如何定位数据倾斜的代码
- 某个task执行特别慢的情况
- 某个task莫名其妙内存溢出的情况
- 查看导致数据倾斜的key的数据分布情况
- 数据倾斜的解决方案
- shuffle调优
- shuffle相关参数调优
Spark性能优化指南(高级篇)
数据倾斜调优
调优概述
- 有的时候,我们可能会遇到大数据计算中最棘手的问题-数据倾斜,此时spark作业的性能会比期望差很多。数据倾斜调优,就是使用各种技术方案解决不同类型的数据倾斜问题,以保证spark作业的性能。
数据倾斜发生时的现象
- 绝大多数task执行的都非常快,但个别task执行极慢,比如,总共有1000个task,997个task都在1分钟之内执行完了,但是剩余两三个task却要一两个小时,这种情况很常见。
- 原本能够正常执行的spark作业,某天突然报出OOM异常,观察异常栈,是我们写的业务代码造成的。
数据倾斜发生的原理
- 数据倾斜的原理很简单:在执行shuffle的时候,必须将各个节点上相同的key拉取到某个节点上的一个task来进行处理,比如按照key进行聚合或join等操作。此时如果某个key对应的数据量特别大的话,就会发生数据倾斜。比如大部分key对应10条数据,但是个别key却对应了100万条数据,那么大部分task就会被分配到10条数据,然后1秒钟就运行完了;但是个别task可能分配了100万数据,要运行1个小时,因此,整个spark作业的运行进度是有运行时间最长的task决定的。
- 因此出现数据倾斜的时候,spark作业看起来会运行的非常慢,甚至看你那个因为某个task处理的数据量过大导致内存溢出。
如何定位数据倾斜的代码
- 数据倾斜只会发生在shuffle过程中,这里罗列一下会触发shuffle操作的算子:distinct, groupByKey, reduceByKey, aggregateByKey, join, cogroup, repartition等。出现数据倾斜时,可能就是代码中使用了这些算子中的某一个导致的。
某个task执行特别慢的情况
- 首先要看数据倾斜发生在第几个stage中
- 如果是yarn-client模式提交,那么本地是可以直接看到log的,可以在log中找到当前运行到了第几个stage
- 如果是yarn-cluster模式提交,则可以通过spark web ui来查看当前运行到了第几个stage
- 无论使用的是何种模式提交,都可以在spark web ui上深入看一下当前这个stage各个task分配的数据量,从而进一步确定是不是task分配的数据不均匀导致了数据倾斜
某个task莫名其妙内存溢出的情况
- 常看异常栈
查看导致数据倾斜的key的数据分布情况
- 知道了数据倾斜发生在哪里之后,通常需要分析一下那个执行了shuffle操作并且导致了数据倾斜的RDD/hive表,查看一下其中key的分布情况。
- 如果是Spark Sql中的group by、join语句导致的数据倾斜,那么就查询一下SQL中使用的表Key分布情况
- 如果是对Spark RDD执行shuffle算子导致的数据倾斜,那么可以在Spark作业中加入查看key分布的代码,比如RDD.countByKey()
数据倾斜的解决方案
- 使用Hive ETL预处理数据
- 过滤少数导致倾斜的key
- 提高shuffle操作的并行度
- 两阶段聚合(局部聚合+全局聚合)
- 将reduce join转为map join
- 采样倾斜key并拆分join操作
- 使用随机前缀和扩容RDD进行join
- 多种方案组合使用
shuffle调优
- 调优概述
大数据spark作业的性能主要就是消耗在了shuffle环节,因为该环节包含了大量的磁盘IO、序列化、网络数据传输等操作。因此,如果要让作业的性能更上一层楼,就有必要对shuffle过程进行调优。影响一个spark作业性能的因素,主要还是代码开发、资源参数以及数据倾斜,shuffle调优只能在整个spark的性能调优中占到一小部分。 - ShuffleManager发展概述
在spark中,负责shuffle过程的执行、计算和处理的组件主要就是ShuffleManager,即shuffle管理器。 - HashShuffleManager
- SortShuffleManager
- bypass运行机制
shuffle相关参数调优
参数 | 默认值 | 参数说明 | 调优建议 |
spark.shuffle.file.buffer | 32k | 该参数用于设置shuffle read task的buffer缓冲大小,而buffer缓冲决定了每次能够拉取多少数据 | 如果作业可用的内存资源较为充足的化,可以适当增加这个参数的大小(比如96m),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能,性能可能会有1%-5%的提升 |
spark.reducer.maxSizeInFlight | 48m | 该参数用于设置shuffle read task的buffer缓冲大小,而buffer缓冲决定了每次能够拉取的数据大小 | 如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如96m),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升 |
spark.shuffle.io.maxRetries | 3 | shuffle read task从shuffle write task所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的,该参数就代表了可以重试的最大次数,如果在指定次数之内还没有拉取成功,就可能导致作业执行失败 | 对于那些包含了特别耗时的shuffle操作的作业,建议增加重试最大次数(比如60次),以避免由于JVM的full gc或者网络不稳定等因素导致的数据拉取失败。 |
spark.shuffle.io.retryWait | 5s | 代表了每次重试拉取数据的等待时间间隔 | 建议加大间隔时长(比如60S),以增加shuffle操作的稳定性 |
spark.shuffle.memoryFraction | 0.2 | 该参数代表了Executor内存中,分配给shuffle read task进行聚合操作的内存比例,默认是20% | 如果内存充足,而且很少使用持久化操作,建议调高这个比例,给shuffle read的聚合操作更多内存,以避免由于内存不足导致聚合过程中频繁读写磁盘,在实践中发现,合理调节该参数可以将性能提升10%左右 |
spark.shuffle.manager | sort | 用于设置ShuffleManager的类型,在spark1.5以后有三个选项,hash,sort,tungsten-sort | 由于SortShuffleManager默认会对数据进行排序,因此如果你的业务逻辑中需要该排序机制的话,则使用默认的SortShuffleManager就可以;而如果你的业务逻辑不需要对数据进行排序,那么建议参考后面的几个参数调优,通过bypass机制或优化的HashShuffleManager来避免排序操作,同时提供较好的磁盘读写性能。这里要注意的是,tungsten-sort要慎用,因为之前发现了一些相应的bug |
spark.shuffle.sort.bypassMergeThreshold | 200 | 当ShuffleManager为SortShuffleManager时,如果shuffle read task的数量小于这个阈值(默认是200),则shuffle write过程中不会进行排序操作,而是直接按照未经优化的HashShuffleManager的方式去写数据,但是最后会将每个task产生的所有临时磁盘文件都合并成一个文件,并会创建单独的索引文件 | 当你使用SortShuffleManager时,如果的确不需要排序操作,那么建议将这个参数调大一些,大于shuffle read task的数量。那么此时就会自动启用bypass机制,map-side就不会进行排序了,减少了排序的性能开销。但是这种方式下,依然会产生大量的磁盘文件,因此shuffle write性能有待提高 |
spark.shuffle.consolidateFiles | false | 如果使用HashShuffleManager,该参数有效。如果设置为true,那么就会开启consolidate机制,会大幅度合并shuffle write的输出文件,对于shuffle read task数量特别多的情况下,这种方法可以极大地减少磁盘IO开销,提升性能 | 如果的确不需要SortShuffleManager的排序机制,那么除了使用bypass机制,还可以尝试将spark.shffle.manager参数手动指定为hash,使用HashShuffleManager,同时开启consolidate机制。在实践中尝试过,发现其性能比开启了bypass机制的SortShuffleManager要高出10%~30% |