一、代码层面的优化
使用高性能算子
使用reduceByKey/aggregateByKey替代groupByKey。
使用mapPartitions替代普通map。
mapPartitions类的算子,一次函数调用会处理一个partition所有的数据,而不是一次函数调用处理一条,性能相对来说会高一些。但是有的时候,使用mapPartitions会出现OOM(内存溢出)的问题。因为单次函数调用就要处理掉一个partition所有的数据,如果内存不够,垃圾回收时是无法回收掉太多对象的,很可能出现OOM异常。所以使用这类操作时要慎重!
使用foreachPartitions替代foreach。
原理类似于“使用mapPartitions替代map”,也是一次函数调用处理一个partition的所有数据,而不是一次函数调用处理一条数据。在实践中发现,foreachPartitions类的算子,对性能的提升还是很有帮助的。比如在foreach函数中,将RDD中所有数据写MySQL,那么如果是普通的foreach算子,就会一条数据一条数据地写,每次函数调用可能就会创建一个数据库连接,此时就势必会频繁地创建和销毁数据库连接,性能是非常低下;但是如果用foreachPartitions算子一次性处理一个partition的数据,那么对于每个partition,只要创建一个数据库连接即可,然后执行批量插入操作,此时性能是比较高的。实践中发现,对于1万条左右的数据量写MySQL,性能可以提升30%以上。
使用filter之后进行coalesce操作。
通常对一个RDD执行filter算子过滤掉RDD中较多数据后(比如30%以上的数据),建议使用coalesce算子,手动减少RDD的partition数量,将RDD中的数据压缩到更少的partition中去。因为filter之后,RDD的每个partition中都会有很多数据被过滤掉,此时如果照常进行后续的计算,其实每个task处理的partition中的数据量并不是很多,有一点资源浪费,而且此时处理的task越多,可能速度反而越慢。因此用coalesce减少partition数量,将RDD中的数据压缩到更少的partition之后,只要使用更少的task即可处理完所有的partition。在某些场景下,对于性能的提升会有一定的帮助。
使用repartitionAndSortWithinPartitions替代repartition与sort类操作。
repartitionAndSortWithinPartitions是Spark官网推荐的一个算子。官方建议,如果是需要在repartition重分区之后还要进行排序,就可以直接使用repartitionAndSortWithinPartitions算子。因为该算子可以一边进行重分区的shuffle操作,一边进行排序。shuffle与sort两个操作同时进行,比先shuffle再sort来说,性能可能是要高的。
尽量减少shuffle相关操作,减少join操作
二、写入数据库时,设置批量插入,关闭事务
result.write.mode(SaveMode.Append).format("jdbc")
.option(JDBCOptions.JDBC_URL,"jdbc:mysql://21.76.120.XX:3306/us_app?rewriteBatchedStatement=true") //开启批量处理
.option("user","root")
.option("password","XXX")
.option(JDBCOptions.JDBC_TABLE_NAME,"tb_pdwqy_qxzh_jcyc")
.option(JDBCOptions.JDBC_TXN_ISOLATION_LEVEL,"NONE") //不开启事务
.option(JDBCOptions.JDBC_BATCH_INSERT_SIZE,500) //设置批量插入数据量
.save()
三、缓存复用数据
如在代码下方反复用到了ZWXSJLFW_Result数据,可以考虑将此数据缓存下来。
val ZWXSJLFW_Result = spark.sql(
"""
|SELECT * from UnionSData_table
|UNION
|SELECT * FROM UnionXData_table
""".stripMargin)
ZWXSJLFW_Result.persist(StorageLevel.DISK_ONLY_2)
ZWXSJLFW_Result.registerTempTable("ZWXSJLFW_Result")
四、参数优化
//开启consolidateFiles
sparkConf.set("spark.shuffle.consolidateFiles","true")
//设置并行度
sparkConf.set("spark.default.parallelism","150")
//设置数据本地化等待时间
sparkConf.set("spark.locality.wait","6s")
//设置mapTask写磁盘缓存
sparkConf.set("spark.shuffle.file.buffer","64k")
//设置byPass机制的触发值
sparkConf.set("spark.shuffle.sort.bypassMergeThreshold","1000")
//设置resultTask拉取缓存
sparkConf.set("spark.reducer.maxSizeInFlight","48m")
//设置重试次数
sparkConf.set("spark.shuffle.io.maxRetries","10")
//设置重试时间间隔
sparkConf.set("spark.shuffle.io.retryWait","10s")
//设置reduce端聚合内存比例
sparkConf.set("spark.shuffle.memoryFraction","0.5")
//设置序列化
sparkConf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
//设置自动分区
sparkConf.set("spark.sql.auto.repartition","true")
//设置shuffle过程中分区数
sparkConf.set("spark.sql.shuffle.partitions","500")
//设置自动选择压缩码
sparkConf.set("spark.sql.inMemoryColumnarStorage.compressed","true")
//关闭自动推测分区字段类型
sparkConf.set("spark.sql.source.partitionColumnTypeInference.enabled","false")
//设置spark自动管理内存
sparkConf.set("spark.sql.tungsten.enabled","true")
//执行sort溢写到磁盘
sparkConf.set("spark.sql.planner.externalSort","true")
//增加executor通信超时时间
sparkConf.set("spark.executor.heartbeatInterval","60s")
//cache限制时间
sparkConf.set("spark.dynamicAllocation.cachedExecutorIdleTimeout","120")
//关闭广播变量
sparkConf.set("spark.sql.autoBroadcastJoinThreshold","-1")
//其他设置
sparkConf.set("spark.sql.files.maxPartitionBytes","268435456")
sparkConf.set("spark.sql.files.openCostInBytes","8388608")
sparkConf.set("spark.debug.maxToStringFields","500")
//推测执行机制
sparkConf.set("spark.speculation","true")
sparkConf.set("spark.speculation.interval","500")
sparkConf.set("spark.speculation.quantile","0.8")
sparkConf.set("spark.speculation.multiplier","1.5")
五、数据库(MySQL)优化
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 10
read_buffer_size = 32M
max_allowed_packet = 64M
table_open_cache = 1024
read_rnd_buffer_size=16M
sort_buffer_size=32M
tmp_table_size=1024M
join_buffer_size=32M
binlog_cache_size = 1M
innodb_buffer_pool_size = 2048M
innodb_log_file_size = 128M
innodb_log_buffer_size = 16M
innodb_flush_log_at_trx_commit = 2
修改/etc/my.cnf 文件,找到
log-bin=mysql-bin
binlog_format=mixed
再这两行前面加上#,将其注释掉,再执行/etc/init.d/mysql restart即可。
六、JVM优化
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
调优总结:
JAVA_OPTS="$JAVA_OPTS -server -Xms3G -Xmx3G -Xss256k -XX:PermSize=128m -XX:MaxPermSize=128m -XX:+UseParallelOldGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/aaa/dump -XX:+PrintGCDetails
-XX:+PrintGCTimeStamps -Xloggc:/usr/jvm/dump/heap_trace.txt -XX:NewSize=1G -XX:MaxNewSize=1G"