一段程序只能完成功能是没有用的,只能能够稳定、高效率地运行才是生成环境所需要的。
本篇记录了Spark各个角度的调优技巧,以备不时之需。
额。。。从最基本的开始讲,可能一些刚接触Spark的人不是很清楚Spark的一些参数变量到底要配置在哪里。
可以通过三种方式配置参数,任选其一皆可。
值得一提的是一个略显诡异的现象,有些参数在spark-env.sh中配置并不起作用,反而要在程序中设置才有效果。
Spark的参数很多,一些默认的设置可以参考官网推荐的配置参数:/docs/latest/configuration.html
可以通过以下几种方式来观察Spark集群的状态和相关性能问题:
前景交代完毕,下面进入正题:
1、小分区合并的问题
由于程序中过度使用filter算子或者使用不当,都会造成大量的小分区出现。
因为每次过滤得到的结果只有原来数据集的一小部分,而这些量很小的数据同样会以一定的分区数并行化分配到各个节点中执行。
带来的问题就是:任务处理的数据量很小,反复地切换任务所消耗的资源反而会带来很大的系统开销。
解决方案:使用重分区函数coalesce进行数据紧缩、减少分区数并设置shuffle=true保证任务是并行计算的
减少分区数,虽然意味着并行度降低,但是相对比之前的大量小任务过度切换的消耗,却是比较值得的。
这里也可以直接使用repartition重分区函数进行操作,因为其底层使用的是coalesce并设置Shuffle=true
2、数据倾斜问题
这是一个生产环境中经常遇到的问题,典型的场景是:大量的数据被分配到小部分节点计算,而其他大部分节点却只计算小部分数据。
问题产生的原因有很多,可能且不全部包括:
可选的解决方案有:
还有一种场景是任务执行速度倾斜问题:集群中其他节点都计算完毕了,但是只有少数几个节点死活运行不完。(其实这和上面的那个场景是差不多的)
解决方案:
3、并行度调整
官方推荐每个CPU CORE分配2-3个任务。
Spark会根据文件大小默认配置Map阶段的任务数,所以我们能够自行调整的就是Reduce阶段的分区数了。
4、DAG调度执行优化
DAG图是Spark计算的基本依赖,所以建议:
尽可能地在Transformation算子中完成对数据的计算,因为过多的Action算子会产生很多多余的Shuffle,在划分DAG图时会形成众多Stage。
1、大任务分发问题
Spark采用Akka的Actor模型来进行消息传递,包括数据、jar包和相关文件等。
而Akka消息通信传递默认的容量最大为10M,一旦传递的消息超过这个限制就会出现这样的错误:
Worker任务失败后Master上会打印“Lost TID:”
根据这个信息找到对应的Worker节点后查看SparkHome/work/目录下的日志,查看Serialized size of result是否超过10M,就可以知道是不是Akka这边的问题了。
一旦确认是Akka通信容量限制之后,就可以通过配置spark.akka.frameSize控制Akka通信消息的最大容量。
2、Broadcast在调优场景的使用
Broadcast广播,主要是用于共享Spark每个Task都会用到的一些只读变量。
对于那些每个Task都会用到的变量来说,如果每个Task都为这些变量分配内存空间显然会使用很多多余的资源,使用广播可以有效的避免这个问题,广播之后,这些变量仅仅会在每台机器上保存一份,有Task需要使用时就到自己的机器上读取就ok。
官方推荐,Task大于20k时可以使用,可以在控制台上看Task的大小。
3、Collect结果过大的问题
大量数据时将数据存储在HDFS上或者其他,不是大量数据,但是超出Akka传输的Buffer大小,通过配置spark.akka.frameSize调整。
1、通过序列化手段优化
序列化之前说过,好处多多,所以是推荐能用就用,Spark上的序列化方式有几种,具体的可以参考官方文档。
这里只简单介绍一下Kryo。
配置参数的时候使用spark.serializer=”org.apache.spark.serializer.KryoSerializer”配置
自定义定义可以被Kryo序列化的类的步骤:
2、通过压缩手段优化
Spark的Job大致可以分为两种:
对于I/O密集型的Job,能压缩就压缩,因为读磁盘的时候数据压缩了,占用空间小了,读取速度不就快了。
对于CPU密集型的Job,看具体CPU使用情况再做决定,因为使用压缩是需要消耗一些CPU资源的,如果当前CPU已经超负荷了,再使用压缩反而适得其反。
Spark支持两种压缩算法:
一些压缩相关的参数配置:
1、对外部资源的批处理操作
如操作数据库时,每个分区的数据应该一起执行一次批处理,而不是一条数据写一次,即map=>mapPartition。
2、reduce和reduceByKey
reduce:内部调用了runJob方法,是一个action操作。
reduceByKey:内部只是调用了combineBykey,是Transformation操作。
大量的数据操作时,reduce汇总所有数据到主节点会有性能瓶颈,将数据转换为Key-Value的形式使用reduceByKey实现逻辑,会做类似mr程序中的Combiner的操作,Transformation操作分布式进行。
3、Shuffle操作符的内存使用
使用会触发Shuffle过程的操作符时,操作的数据集合太大造成OOM,每个任务执行过程中会在各自的内存创建Hash表来进行数据分组。
可以解决的方案可能有: