由于大多数Spark计算的内存性质,Spark程序可能会受到群集中任何资源(CPU,网络带宽或内存)的瓶颈。通常,如果数据适合内存,则瓶颈是网络带宽,但是有时,您还需要进行一些调整,例如 以序列化形式存储RDD,以减少内存使用量。对于大多数程序,切换到Kryo序列化并以序列化形式保留数据将解决大多数常见的性能问题(官网提示)。
1. 常规性能调优
一:最优资源配置
Spark性能调优的第一步,就是为任务分配更多的资源,在一定范围内,增加资源的分配与性能的提升是成正比的,实现了最优的资源配置后,在此基础上再考虑进行后面论述的性能调优策略。
资源的分配在使用脚本提交Spark任务时进行指定,标准的Spark任务提交脚本所示:
/usr/opt/modules/spark/bin/spark-submit \ --class com.atguigu.spark.Analysis \ --num-executors 80 \ --driver-memory 6g \ --executor-memory 6g \ --executor-cores 3 \ /usr/opt/modules/spark/jar/spark.jar \
可以进行分配的资源如表:
名称 说明 --num-executors 配置Executor的数量 --driver-memory 配置Driver内存(影响不大) --executor-memory 配置每个Executor的内存大小 --executor-cores 配置每个Executor的CPU core数量
调节原则:尽量将任务分配的资源调节到可以使用的资源的最大限度。
对于具体资源的分配,分别讨论Spark的两种Cluster运行模式:
第一种是Spark Standalone模式,你在提交任务前,一定知道或者可以从运维部门获取到你可以使用的资源情况,在编写submit脚本的时候,就根据可用的资源情况进行资源的分配,比如说集群有15台机器,每台机器为8G内存,2个CPU core,那么就指定15个Executor,每个Executor分配8G内存,2个CPU core。
第二种是Spark Yarn模式,由于Yarn使用资源队列进行资源的分配和调度,在表写submit脚本的时候,就根据Spark作业要提交到的资源队列,进行资源的分配,比如资源队列有400G内存,100个CPU core,那么指定50个Executor,每个Executor分配8G内存,2个CPU core。关于yarn的资源情况可以参照生成环境的配置,最好是可以看一下机器的cpu ,core,vcore以及内存的配置,在保证系统正常运行的情况下,尽量把资源用充分。往往生产环境会配置vcore,2个cpu,每个16核,可能有72个vcore而且内存大多有180G
对表中的各项资源进行了调节后,得到的性能提升如表所示:
名称 | 解析 |
---|---|
增加Executor·个数 | 在资源允许的情况下,增加Executor的个数可以提高执行task的并行度。比如有4个Executor,每个Executor有2个CPU core,那么可以并行执行8个task,如果将Executor的个数增加到8个(资源允许的情况下),那么可以并行执行16个task,此时的并行能力提升了一倍。 |
增加每个Executor的CPU core个数 | 在资源允许的情况下,增加每个Executor的Cpu core个数,可以提高执行task的并行度。比如有4个Executor,每个Executor有2个CPU core,那么可以并行执行8个task,如果将每个Executor的CPU core个数增加到4个(资源允许的情况下),那么可以并行执行16个task,此时的并行能力提升了一倍。 |
增加每个Executor的内存量 | 在资源允许的情况下,增加每个Executor的内存量以后,对性能的提升有三点:可以缓存更多的数据(即对RDD进行cache),写入磁盘的数据相应减少,甚至可以不写入磁盘,减少了可能的磁盘IO;可以为shuffle操作提供更多内存,即有更多空间来存放reduce端拉取的数据,写入磁盘的数据相应减少,甚至可以不写入磁盘,减少了可能的磁盘IO;可以为task的执行提供更多内存,在task的执行过程中可能创建很多对象,内存较小时会引发频繁的GC,增加内存后,可以避免频繁的GC,提升整体性能 |
生产环境Spark submit脚本配置
/usr/local/spark/bin/spark-submit \ --class com.atguigu.spark.WordCount \ --num-executors 80 \ --driver-memory 6g \ --executor-memory 6g \ --executor-cores 3 \ --master yarn-cluster \ --queue root.default \ --conf spark.yarn.executor.memoryOverhead=2048 \ --conf spark.core.connection.ack.wait.timeout=300 \ /usr/local/spark/spark.jar 参数配置参考值: --num-executors:50~100 --driver-memory:1G~5G --executor-memory:6G~10G --executor-cores:3 --master:实际生产环境一定使用yarn-cluster --client :好处在于可以打印一下log信息,便于调试
二:RDD优化
①RDD复用
在对RDD进行算子时,要避免相同的算子和计算逻辑之下对RDD进行重复的计算,如图:
对图中的RDD计算架构进行修改,得到如图所示的优化结果:
②RDD持久化
在Spark中,当多次对同一个RDD执行算子操作时,每一次都会对这个RDD以之前的父RDD重新计算一次,这种情况是必须要避免的,对同一个RDD的重复计算是对资源的极大浪费,因此,必须对多次使用的RDD进行持久化,通过持久化将公共RDD的数据缓存到内存/磁盘中,之后对于公共RDD的计算都会从内存/磁盘中直接获取RDD数据。
对于RDD的持久化,有两点需要说明:
第一,RDD的持久化是可以进行序列化的,当内存无法将RDD的数据完整的进行存放的时候,可以考虑使用序列化的方式减小数据体积,将数据完整存储在内存中。
第二,如果对于数据的可靠性要求很高,并且内存充足,可以使用副本机制,对RDD数据进行持久化。当持久化启用了复本机制时,对于持久化的每个数据单元都存储一个副本,放在其他节点上面,由此实现数据的容错,一旦一个副本数据丢失,不需要重新计算,还可以使用另外一个副本。