Spark 技术调优,别告诉我你不会?

目录

 

一、性能调优

二、jvm调优 

三、shuffle调优(优先使用前面两点,实测有效)

四、算子调优

五、troubleshooting

六、数据倾斜解决方案


一、性能调优

1.1 配更多资源:

    --num-executors 3 \  配置executor的数量--driver-memory 100m \  配置driver的内存(影响不大) --executor-memory 100m \  配置每个executor的内存大小 --executor-cores 3 \  配置每个executor的cpu core数量

    num-executors、executor-cores可提升任务的并行度;driver-memory、executor-memory增加内存,可缓存更多的数据减少磁盘IO,减少suffle时reduce端磁盘IO,可降低堆内存满了频繁GC,避免频繁垃圾回收

扫描二维码关注公众号,回复: 12014122 查看本文章

1.2 调节并行度:

    1、task数量,至少设置成与Spark application的总cpu core数量相同(最理想情况,比如总共150个cpu core,分配了150个task,一起运行,差不多同一时间运行完毕)

    2、官方是推荐,task数量,设置成spark application总cpu core数量的2~3倍,比如150个cpu core,基本要设置task数量为300~500;

    如何设置一个Spark Application的并行度? SparkConf conf = new SparkConf()  .set("spark.default.parallelism", "500")

1.3 重构RDD架构以及RDD持久化:

    第一,RDD架构重构与优化 尽量去复用RDD,差不多的RDD,可以抽取称为一个共同的RDD,供后面的RDD计算时,反复使用。

    第二,公共RDD一定要实现持久化,对于要多次计算和使用的公共RDD,一定要进行持久化。

    第三,持久化,是可以进行序列化的。优化使用memory,再考虑磁盘

    第四,为了数据的高可靠性,而且内存充足,可以使用双副本机制,进行持久化;持久化后的一个副本,因为机器宕机了,副本丢了,就还是得重新计算一次;这种方式,仅仅针对你的内存资源极度充足

1.4 广播大变量

    广播变量的好处,不是每个task一份变量副本,而是变成每个节点的executor才一份副本。这样的话,就可以让变量产生的副本大大减少。减少网络开销、内存占用、磁盘IO、GC垃圾回收的次数

1.5 使用Kryo序列化set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")

    Spark内部是使用Java的序列化机制,ObjectOutputStream / ObjectInputStream,对象输入输出流机制,来进行序列化

    Spark支持使用Kryo序列化机制。Kryo序列化机制,比默认的Java序列化机制,速度要快,序列化后的数据要更小,大概是Java序列化机制的1/10。 所以Kryo序列化优化以后,可以让网络传输的数据变少;在集群中耗费的内存资源大大减少。

1.6 使用fastutil优化数据格式

    fastutil尽量提供了在任何场景下都是速度最快的集合类库

1.7 调节数据本地化等待时长

    BlockManager > PROCESS_LOCAL > NODE_LOCAL > NO_PREF > RACK_LOCAL > ANY    

    观察日志,spark作业的运行日志,推荐大家在测试的时候,先用client模式,在本地就直接可以看到比较全的日志。日志里面会显示,starting task。。。,PROCESS LOCAL、NODE LOCAL 观察大部分task的数据本地化级别

    new SparkConf()  .set("spark.locality.wait", "10") 

BlockManager

存放位置

详细说明

PROCESS_LOCAL

进程本地化 

task要计算的数据在同一个Executor中

NODE_LOCAL

节点本地化  

速度比PROCESS_LOCAL稍慢,因为数据需要在不同进程之间传递或从文件中读取

NO_PREF

没有最佳位置这一说

数据从哪里访问都一样快,不需要位置优先。比如SparkSQL读取MySQL中的数据

RACK_LOCAL

本架本地化

数据在同一机架的不同节点。需要通过网络传输数据及文件IO,比NODE_LOCAL慢

ANY

跨机架

数据在非同一机架的网络上,速度最慢


二、jvm调优 

2.1 JVM调优之降低cache操作的内存占比

    JVM调优的第一个点:降低cache操作的内存占比 spark中,堆内存又被划分成了两块儿,一块儿是专门用来给RDD的cache、persist操作进行RDD数据缓存用的;

    另外一块儿,就是我们刚才所说的,用来给spark算子函数的运行使用的,存放函数中自己创建的对象

    spark.storage.memoryFraction,0.6 -> 0.5 -> 0.4 -> 0.2

2.2 JVM调优之调节executor堆外内存与连接等待时长

    --conf spark.yarn.executor.memoryOverhead=2048(最小300m),一般会调大些

    --conf spark.core.connection.ack.wait.timeout=300(某某file,not found。file lost。很有可能是有那份数据的executor在jvm gc。所以拉取数据的时候,建立不了连接。然后超过默认60s以后,直接宣告失败。)


三、shuffle调优(优先使用前面两点,实测有效)

3.1 Shuffle调优之合并map端输出文件

    new SparkConf().set("spark.shuffle.consolidateFiles", "true") 开启shuffle map端输出文件合并的机制;默认情况下,是不开启的,就是会发生如上所述的大量map端输出文件的操作,严重影响性能。

    没有开启的话,每一个task都会创建一份文件,前后有多少个task创建就会有多少个文件生成;如果开启的话,就会生成task的并行度 * executor的数量 份文件

3.2 Shuffle调优之调节map端内存缓冲与reduce端内存占比

    spark.shuffle.file.buffer,默认32k,可以调成64K      // spark.shuffle.file.buffer,每次扩大一倍,然后看看效果,64,128;spark.shuffle.memoryFraction,每次提高0.1,看看效果。

    spark.shuffle.memoryFraction,默认0.2,可以调成0.3   // 如果数据量比较大,reduce task拉取过来的数据很多,那么就会频繁发生reduce端聚合内存不够用,频繁发生spill操作,溢写到磁盘上去。

3.3 Shuffle调优之HashShuffleManager与SortShuffleManager

  1. 在生产环境中,不建议大家贸然使用第三点和第四点:
  2. 如果你不想要你的数据在shuffle时排序,那么就自己设置一下,用hash shuffle manager。 
  3. 如果你的确是需要你的数据在shuffle时进行排序的,那么就默认不用动,默认就是sort shuffle manager;或者是什么?如果你压根儿不care是否排序这个事儿,那么就默认让他就是sort的。调节一些其他的参数(consolidation机制)。(80%,都是用这种) 
    spark.shuffle.manager:hash、sort、tungsten-sort 
    new SparkConf().set("spark.shuffle.manager", "hash") 
    new SparkConf().set("spark.shuffle.manager", "tungsten-sort") 
    // 默认就是,new SparkConf().set("spark.shuffle.manager", "sort") 
    new SparkConf().set("spark.shuffle.sort.bypassMergeThreshold", "550")

四、算子调优

4.1 MapPartitions提升Map类操作性能

    什么时候比较适合用MapPartitions系列操作,就是说,数据量不是特别大的时候,都可以用这种MapPartitions系列操作,性能还是非常不错的,是有提升的。比如原来是15分钟,(曾经有一次性能调优),12分钟。10分钟->9分钟。

4.2 filter过后使用coalesce减少分区数量

    主要就是用于在filter操作之后,针对每个partition的数据量各不相同的情况,来压缩partition的数量。减少partition的数量,而且让每个partition的数据量都尽量均匀紧凑。 从而便于后面的task进行计算操作,能够一定程度的提升性能。

4.3 使用foreachPartition优化写数据库性能

    在实际生产环境中,清一色,都是使用foreachPartition操作;但是有个问题,跟mapPartitions操作一样,如果一个partition的数量真的特别特别大,比如真的是100万,那基本上就不太靠谱了。 一下子进来,很有可能会发生OOM,内存溢出的问题。

4.4 使用repartition解决Spark SQL低并行度的性能问题

    repartition算子,你用Spark SQL这一步的并行度和task数量,肯定是没有办法去改变了。但是呢,可以将你用Spark SQL查询出来的RDD,使用repartition算子,去重新进行分区,此时可以分区成多个partition,比如从20个partition,分区成100个。

4.5 reduceByKey本地聚合

    reduceByKey,相较于普通的shuffle操作(比如groupByKey),它的一个特点,就是说,会进行map端的本地聚合。


五、troubleshooting

5.1 控制shuffle reduce端缓冲大小以避免OOM

    reduce端执行的聚合函数的代码,可能会创建大量的对象。也许,一下子,内存就撑不住了,就会OOM。这个时候,就应该减少reduce端task缓冲的大小。我宁愿多拉取几次,但是每次同时能够拉取到reduce端每个task的数量,比较少,就不容易发生OOM内存溢出的问题。(比如,可以调节成12M)

    spark.reducer.maxSizeInFlight,48
    spark.reducer.maxSizeInFlight,24

5.2 解决JVM GC导致的shuffle文件拉取失败

spark.shuffle.io.maxRetries 3

    第一个参数,意思就是说,shuffle文件拉取的时候,如果没有拉取到(拉取失败),最多或重试几次(会重新拉取几次文件),默认是3次。 

    spark.shuffle.io.retryWait 5s 第二个参数,意思就是说,每一次重试拉取文件的时间间隔,默认是5s钟。

5.3 解决YARN队列资源不足导致的application直接失败

    可能同时提交了相同的作业,之前那个作业就占据了资源的60%,再提交一个相同的作业,肯定会资源不足;或者提交了一个长时间的作业,而后面需要运行2分钟的作业

    解决:跟运维沟通,实现调度策略。zeus

5.4 解决各种序列化导致的报错

    序列化报错要注意的三个点: 

  1. 你的算子函数里面,如果使用到了外部的自定义类型的变量,那么此时,就要求你的自定义类型,必须是可序列化的
  2. 如果要将自定义的类型,作为RDD的元素类型,那么自定义的类型也必须是可以序列化的
  3. 不能在上述两种情况下,去使用一些第三方的,不支持序列化的类型

    Connection是不支持序列化的

5.5 解决算子函数返回NULL导致的问题

    大家可以看到,在有些算子函数里面,是需要我们有一个返回值的。但是,有时候,我们可能对某些值,就是不想有什么返回值。

    我们如果直接返回NULL的话,那么可以不幸的告诉大家,是不行的,会报错的。 Scala.Math(NULL),异常 如果碰到你的确是对于某些值,不想要有返回值的话,有一个解决的办法: 

  1. 在返回的时候,返回一些特殊的值,不要返回null,比如“-999” 
  2. 在通过算子获取到了一个RDD之后,可以对这个RDD执行filter操作,进行数据过滤。filter内,可以对数据进行判定,如果是-999,那么就返回false,给过滤掉就可以了。 
  3. 大家不要忘了,之前咱们讲过的那个算子调优里面的coalesce算子,在filter之后,可以使用coalesce算子压缩一下RDD的partition的数量,让各个partition的数据比较紧凑一些。也能提升一些性能。

5.6 解决yarn-client模式导致的网卡流量激增问题

    yarn-client模式下,只是用于测试时使用;

    yarn-cluster模式,就跟你的本地机器引起的网卡流量激增的问题,就没有关系了。也就是说,就算有问题,也应该是yarn运维团队和基础运维团队之间的事情了。

    使用了yarn-cluster模式以后,就不是你的本地机器运行Driver,进行task调度了。是yarn集群中,某个节点会运行driver进程,负责task调度。

5.7 解决yarn-cluster模式的JVM栈内存溢出问题

    yarn-client模式下,driver是运行在本地机器上的,JVM的永久代的大小是128M,这个是没有问题的

    yarn-cluster模式下,driver是运行在yarn集群的某个节点上的,使用的是没有经过配置的默认设置(PermGen永久代大小),82M。

    解决:spark-submit脚本中,加入以下配置即可:--conf spark.driver.extraJavaOptions="-XX:PermSize=128M -XX:MaxPermSize=256M"

    问题二:spark sql,调用的方法层级过多,因为产生了大量的,非常深的,超出了JVM栈深度限制的,递归。

    解决:JVM Stack Memory Overflow,栈内存溢出。 这种时候,建议不要搞那么复杂的spark sql语句。采用替代方案:将一条sql语句,拆解成多条sql语句来执行。每条sql语句,就只有100个or子句以内;一条一条SQL语句来执行。根据生产环境经验的测试,一条sql语句,100个or子句以内,是还可以的。通常情况下,不会报那个栈内存溢出。

5.8 错误的持久化方式以及checkpoint的使用


六、数据倾斜解决方案

6.1 聚合源数据以及过滤导致倾斜的key

  • 第一个方案:聚合源数据;将数据按key,将valuse进行聚合,中间使用分隔符进行拼接
  • 第二个方案:过滤导致倾斜的key

    简单。直接。效果是非常之好的。彻底根除了数据倾斜的问题。

6.2 提高shuffle操作reduce并行度

    所有的shuffle算子,比如groupByKey、countByKey、reduceByKey。在调用的时候,传入进去一个参数。一个数字。那个数字,就代表了那个shuffle操作的reduce端的并行度。那么在进行shuffle操作的时候,就会对应着创建指定数量的reduce task。 这样的话,就可以让每个reduce task分配到更少的数据。基本可以缓解数据倾斜的问题。

    比如说,原本某个task分配数据特别多,直接OOM,内存溢出了,程序没法运行,直接挂掉。按照log,找到发生数据倾斜的shuffle操作,给它传入一个并行度数字,这样的话,原先那个task分配到的数据,肯定会变少。就至少可以避免OOM的情况,程序至少是可以跑的。

6.3 使用随机key实现双重聚合

    第一轮聚合的时候,对key进行打散,将原先一样的key,变成不一样的key,相当于是将每个key分为多组; 

    先针对多个组,进行key的局部聚合;接着,再去除掉每个key的前缀,然后对所有的key,进行全局的聚合。 

    对groupByKey、reduceByKey造成的数据倾斜,有比较好的效果。 

    如果说,之前的第一、第二、第三种方案,都没法解决数据倾斜的问题,那么就只能依靠这一种方式了。

6.4 将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比较小的情况下。

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

    将发生数据倾斜的key,单独拉出来,放到一个RDD中去;就用这个原本会倾斜的key RDD跟其他RDD,单独去join一下,这个时候,key对应的数据,可能就会分散到多个task中去进行join操作。 

    就不至于说是,这个key跟之前其他的key混合在一个RDD中时,肯定是会导致一个key对应的所有数据,都到一个task中去,就会导致数据倾斜。

6.6 使用随机数以及扩容表进行join

  1. 选择一个RDD,要用flatMap,进行扩容,将每条数据,映射为多条数据,每个映射出来的数据,都带了一个n以内的随机数,通常来说,会选择10。 
  2. 将另外一个RDD,做普通的map映射操作,每条数据,都打上一个10以内的随机数。 
  3. 最后,将两个处理后的RDD,进行join操作。

猜你喜欢

转载自blog.csdn.net/weixin_32265569/article/details/109267815