Spark性能优化一篇就够了

一,Spark性能优化:开发调优

1.避免创建重复的RDD
对于同一份数据,只应该创建一个RDD,不能创建多个RDD来代表同一份数据。否则Spark作业会进行多次重复计算多个代表相同数据的RDD,进而增加了作业的性能开销。
2.尽可能复用同一个RDD
对于多个RDD的数据有重叠或者包含的情况,我们应该尽量复用一个RDD,这样可以尽可能地减少RDD的数量,从而尽可能减少算子执行的次数。
3.对多次使用的RDD进行持久化或chickPoint
每次你对一个RDD执行一个算子操作时,都会重新从源头处计算一遍,计算出那个RDD来,然后再对这个RDD执行你的算子操作。对多次使用的RDD进行持久化。此时Spark就会根据你的持久化策略,将RDD中的数据保存到内存或者磁盘中。以后每次对这个RDD进行算子操作时,都会直接从内存或磁盘中提取持久化的RDD数据,避免重复计算.
持久化策略,默认情况下性能最好的是MEMORY_ONLY,但前提是内存必须足够大,能够存放下整个RDD的所有数据。避免了序列化与反序列化操作性能的开销。
如果RDD数据较多时会导致jvm的内存溢出(OOM),那么建议尝试使用MEMORY—ONLY_SER,改级别将RDD数据序列化再保存在内存中,此时的partition仅仅是一个字节数组,大大减少对象数量,降低了内存的的占用,这个级别比MEMORY_ONLY多出来序列化和反序列化的性能开销,如果RDD数量过多的话,还是会导致内存溢出,那么就建议使用MEMORY_AND_DISK_SER该策略会优先尝试将数据缓存在内存中,内存放不下才会写入磁盘
使用RDD.persist(StorageLevel.MEMORY_ONLY_SER)这样的语法即可。
因此,对于序列化的持久化级别,还可以进一步优化,也就是说,使用Kryo序列化类库,这样,可以获得更快的序列化速度,并且占用更小的内存空间,但是要记住,如果RDD的元素是自定义类型的话,在Kryo中提前注册自定义类型。如果要保证在RDD的持久化数据可能丢失的情况下,还要保证高性能,那么可以对RDD进行Checkpoint操作。
4.尽量避免使用shuffle类算子
在Spark作业运行过程中,最消耗性能的地方就是shuffle过程,在我们的开发过程中,能避免则尽可能避免使用reduceByKey、join、distinct、repartition等会进行shuffle的算子,尽量使用map类的非shuffle算子。这样的话,没有shuffle操作或者仅有较少shuffle操作的Spark作业,可以大大减少性能开销。
5.使用高性能的算子
1)reduceByKey和groupByKey的合理使用
在可能的情况下建议使用reduceByKey替换调groupByKey,因为reduceByKey算子会使用自定义函数对每个节点本地相同的key进行预聚合,而groupByKey不会进行预聚合的,全量的数据会在集群的各个界定啊之间分发和传输,性能相对来说比较差
2)使用mapPartitons替代普通map
mapPartitions类的算子,一次函数调用会处理一个partition所有的数据,而不是一次函数调用处理一条,性能相对来说会高一些。
3) 使用foreachPartitions替代foreach
也是一次函数调用处理一个partition的所有数据,而不是一次函数调用处理一条数据。对性能的提升还是有帮助的
4)使用filter之后进行coalesce操作
通常对一个RDD执行filter算子过滤掉较多数据后,建议使用coalesce算子,手动减少RDD的partition数量,将RDD中的数据压缩到更少的额partition之后,只要使用更少的task即可
处理完所有的partition.
6.广播大变量
在算子函数使用到外部变量是,默认情况下Spark会讲该变量赋值多个副本,通过网络传输到task中,每个task都有一个副本,如果变量本身比较大的话,那么大量的变量副本在网络中传输的性能开销以及在各节点的Excutor中占用过多内存导致频繁的GC,极大的影响性能。因此,建议使用Spark的广播功能,对改变量进行广播,保证每个Executor的内存中,只驻留一份变量副本。task执行时共享该副本。从而减少网络传输的性能开销,减少Executor内存的占用开销,降低GC的频率.
7.使用Kryo优化序列化性能
如果要使用Kryo序列化机制,首先要用SparkConf设置一个参数,使用new SparkConf().set(“spark.serializer”, “org.apache.spark.serializer.KryoSerializer”)即可。使用Kryo时,它要求是需要序列化类,要先进行注册的,已获得最佳性能,如果不注册的话,Kryo必须时刻保存类型的全权限定名,反而占用不少内存。
8.优化数据结构
1).优先使用数组以及字符串,而不是集合类。也就是说,优先用array,而不是ArrayList、LinkedList、HashMap等集合。
2).避免使用多层嵌套的对象结构。
3).对于有些能够避免的场景,尽量使用int替代String。
在Spark编码实现中,特别是对于算子函数中的代码尽量使用字符串代替对象,使用原始类型(int,Long)替代字符串,使用数组替代集合类型,这样尽可能的减少内存占用,从而降低GC频率,提升性能.

二,Spark性能优化:资源调优

1.num-executors
该参数用于设置spark作业总共要用多少个Executor进程来执行,Driver向Yarn集群真情资源时,Yarn尽可能按照你的使者4在进群各个节点上启动响应数量的Executor进程。如果不设置的话,默认只会
给你启动少量的Executor进程,此时你的spark运行速度非常慢。
建议一般设置50~100个左右比表合适,太少的话无法充分利用集群资源,太多的话,大部分队列可能无法给予充分资源。
2.executor-memory
该参数用于设置每个Executor进程的内存。Executor内存的大小,很多时候直接决定了Spark作业的性能.
建议,每个executor进程的内存设置4~8g较为合适,具体的设置还是得根据不同部门的资源队列来定。
3.executor-cores
该参数用于设置每个Executor进程的CPU core数量。这个参数决定了每个Executor进程并行执行task线程的能力。
Executor的CPU core数量设置为2~4个较为合适。同样得根据不同部门的资源队列来定,可以看看自己的资源队列的最大CPU core限制是多少,再依据设置的Executor数量,来决定每个Executor进程可以分配到几个CPU core
4.driver-memory
该参数用于设置Driver进程的内存
Driver的内存通常来说不设置,或者设置1G左右应该就够了。需要使用的collect算子将RDD数据拉倒Driver上进行处理,那么必须Driver的内存足够大,否则内存溢出。
5.spark.default.parallelism
该参数用于设置每个stage的默认task数量。这个参数极为重要,如果不设置可能会直接影响你的Spark作业性能.
建议默认task数量为500~1000个较为合适。如果不设置,spark自己根据底层HDFS的block数量来设置task数量,默认一个HDFS block对应一个task。导致很多Executor进程没有task执行,白白浪费资源。
6.spark.storage.memoryFraction
该参数用于设置RDD持久化数据在Executor内存中能占的比例,默认是0.6。默认Executor 60% 的内存可以用来保持持久化RDD 数据.
如果Spark作业中,有较多的RDD持久化操作,该参数的值可以适当提高一些,保证持久化的数据能够容纳在内存中。避免内存不够缓存所有的数据,导致数据只能写入磁盘中,降低了性能。
7.spark.shuffle.memoryFraction
该参数用于设置shuffle过程中一个task拉取到上个stage的task的输出后,进行聚合操作时能够使用的Executor内存的比例,默认是0.2。
如果Spark作业中的RDD持久化操作较少,shuffle操作较多时,建议降低持久化操作的内存占比,提高shuffle操作的内存占比比例,避免shuffle过程中数据过多时内存不够用,必须溢写到磁盘上,降低了性能。
此外,如果作业频繁GC导致运行缓慢,意味着task执行用户代码的内存不够用,那么同样建议调低这个参数的值。

三,Spark性能优化:数据倾斜调优

1.过滤少数导致倾斜的key
如果发现导致倾斜的key就少数几个,而且对计算本身的影响并不大的话,那么很适合使用这种方案
原理:将导致数据倾斜的key给过滤掉之后,这些key就不会参与计算了,自然不可能产生数据倾斜。
优点:实现简单,而且效果也很好,可以完全规避掉数据倾斜。
缺点:适用场景不多,大多数情况下,导致倾斜的key还是很多的,并不是只有少数几个。
2.提高shuffle操作的并行度
优先使用这种方案,因为这是处理数据倾斜最简单的一种方案。
原理:增加shuffle read task的数量,可以让原本分配给一个task的多个key分配给多个task,从而让每个task处理比原来更少的数据
优点:实现起来比较简单,可以有效缓解和减轻数据倾斜的影响。
缺点:只是缓解了数据倾斜而已,没有彻底根除问题,其效果有限。
3.两阶段聚合(局部聚合+全局聚合)
对RDD执行reduceByKey等聚合类shuffle算子或者在Spark SQL中使用group by语句进行分组聚合时,比较适用这种方案。
原理:将原本相同的key通过附加随机前缀的方式,变成多个不同的key,就可以让原本被一个task处理的数据分散到多个task上去做局部聚合,进而解决单个task处理数据量过多的问题。接着去除掉随机前缀,再次进行全局聚合,就可以得到最终的结果。
优点:对于聚合类的shuffle操作导致的数据倾斜,效果是非常不错的。通常都可以解决掉数据倾斜,或者至少是大幅度缓解数据倾斜,将Spark作业的性能提升数倍以上。
缺点:仅仅适用于聚合类的shuffle操作,适用范围相对较窄。如果是join类的shuffle操作,还得用其他的解决方案。
4.将reduce join转为map join
在对RDD使用join类操作,或者是在Spark SQL中使用join语句时,而且join操作中的一个RDD或表的数据量比较小
原理:普通的join是会走shuffle过程的,而一旦shuffle,就相当于会将相同key的数据拉取到一个shuffle read task中再进行join,此时就是reduce join。但是如果一个RDD是比较小的,则可以采用广播小RDD全量数据+map算子来实现与join同样的效果,也就是map join,此时就不会发生shuffle操作,也就不会发生数据倾斜。
优点:对join操作导致的数据倾斜,效果非常好,因为根本就不会发生shuffle,也就根本不会发生数据倾斜。
缺点:适用场景较少,因为这个方案只适用于一个大表和一个小表的情况。毕竟我们需要将小表进行广播,此时会比较消耗内存资源

四,Spark性能优化:shuffle调优

1.spark.shuffle.file.buffer
该参数用于设置shuffle write task 的bufferOutputStream的buffer缓冲大小。默认值是32K.将数据写入磁盘文件之前,会先写入buffer缓冲区中,待缓冲写满后,写磁盘
建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如64k),从而减少shuffle write过程中溢写磁盘文件的次数,也就可以减少磁盘IO次数,进而提升性能。
2.spark.reducer.maxSizeInFlight
该参数用于设置shuffle read task的buffer缓冲大小,默认48m而这个buffer缓冲决定了每次能够拉取多少数据。
建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如96m),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。
3.spark.shuffle.io.maxRetries
默认:3
shuffle read task从shuffle write task所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的。该参数就代表了可以重试的最大次数
建议:对于那些包含了特别耗时的shuffle操作的作业,建议增加重试最大次数(比如60次)
4.spark.shuffle.io.retryWait
默认值5s
该参数代表了每次重试拉取数据的等待间隔,默认是5s。
建议:建议加大间隔时长(比如60s),以增加shuffle操作的稳定性。
5.spark.shuffle.memoryFraction
默认值:0.2
参数说明:该参数代表了Executor内存中,分配给shuffle read task进行聚合操作的内存比例,默认是20%。
调优建议:在资源参数调优中讲解过这个参数。如果内存充足,而且很少使用持久化操作,建议调高这个比例
6.spark.shuffle.manager
默认值:sort
该参数用于设置ShuffleManager的类型。
建议:由于SortShuffleManager默认会对数据进行排序,因此如果你的业务逻辑中需要该排序机制的话,则使用默认的SortShuffleManager就可以;
7.spark.shuffle.consolidateFiles
默认值:false
如果使用HashShuffleManager,该参数有效。如果设置为true,那么就会开启consolidate机制,会大幅度合并shuffle write的输出文件,对于shuffle read task数量特别多的情况下,这种方法可以极大地减少磁盘IO开销,提升性能。
建议:如果的确不需要SortShuffleManager的排序机制,那么除了使用bypass机制,还可以尝试将spark.shffle.manager参数手动指定为hash,使用HashShuffleManager,同时开启consolidate机制。

猜你喜欢

转载自blog.csdn.net/weixin_43777152/article/details/109255182