spark性能优化以及问题解决方式

 内存/GC优化

      程序的稳定性有所提升,但是让我们完全跑通的最后一根救命稻草是内存、GC相关的优化。

Direct Memory

我们使用的spark版本是1.5.2(更准确的说是1.5.3-shapshot),shuffle过程中block的传输使用netty(spark.shuffle.blockTransferService)。基于netty的shuffle,使用direct memory存进行buffer(spark.shuffle.io.preferDirectBufs),所以在大数据量shuffle时,堆外内存使用较多。当然,也可以使用传统的nio方式处理shuffle,但是此方式在spark 1.5版本设置为deprecated,并将会在1.6版本彻底移除,所以我最终还是采用了netty的shuffle。

jvm关于堆外内存的配置相对较少,通过-XX:MaxDirectMemorySize可以指定最大的direct memory。默认如果不设置,则与最大堆内存相同。

Direct Memory是受GC控制的,例如ByteBuffer bb = ByteBuffer.allocateDirect(1024),这段代码的执行会在堆外占用1k的内存,Java堆内只会占用一个对象的指针引用的大小,堆外的这1k的空间只有当bb对象被回收时,才会被回收,这里会发现一个明显的不对称现象,就是堆外可能占用了很多,而堆内没占用多少,导致还没触发GC。加上-XX:MaxDirectMemorySize这个大小限制后,那么只要Direct Memory使用到达了这个大小,就会强制触发GC,这个大小如果设置的不够用,那么在日志中会看到java.lang.OutOfMemoryError: Direct buffer memory。

例如,在我们的例子中,发现堆外内存飙升的比较快,很容易被yarn kill掉,所以应适当调小-XX:MaxDirectMemorySize(也不能过小,否则会报Direct buffer memory异常)。当然你也可以调大spark.yarn.executor.memoryOverhead,加大yarn对我们使用内存的宽容度,但是这样比较浪费资源了。

GC优化

GC优化前,最好是把gc日志打出来,便于我们进行调试。

--conf spark.executor.extraJavaOptions="-XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintHeapAtGC -XX:+PrintGCApplicationConcurrentTime -Xloggc:gc.log"

通过看gc日志,我们发现一个case,特定时间段内,堆内存其实很闲,堆内存使用率也就5%左右,长时间不进行父gc,导致Direct Memory一直不进行回收,一直在飙升。所以,我们的目标是让父gc更频繁些,多触发一些Direct Memory回收。

第一,可以减少整个堆内存的大小,当然也不能太小,否则堆内存也会报OOM。这里,我配置了1G的最大堆内存。

第二,可以让年轻代的对象尽快进入年老代,增加年老代的内存。这里我使用了-Xmn100m,将年轻代大小设置为100M。另外,年轻代的对象默认会在young gc 15次后进入年老代,这会造成年轻代使用率比较大,young gc比较多,但是年老代使用率低,父gc比较少,通过配置-XX:MaxTenuringThreshold=1,年轻代的对象经过一次young gc后就进入年老代,加快年老代父gc的频率。

第三,可以让年老代更频繁的进行父gc。一般年老代gc策略我们主要有-XX:+UseParallelOldGC和-XX:+UseConcMarkSweepGC这两种,ParallelOldGC吞吐率较大,ConcMarkSweepGC延迟较低。我们希望父gc频繁些,对吞吐率要求较低,而且ConcMarkSweepGC可以设置-XX:CMSInitiatingOccupancyFraction,即年老代内存使用率达到什么比例时触发CMS。我们决定使用CMS,并设置-XX:CMSInitiatingOccupancyFraction=10,即年老代使用率10%时触发父gc。

通过对GC策略的配置,我们发现父gc进行的频率加快了,带来好处就是Direct Memory能够尽快进行回收,当然也有坏处,就是gc时间增加了,cpu使用率也有所增加。

最终我们对executor的配置如下:

--executor-memory 1G --num-executors 160 --executor-cores 1 --conf spark.yarn.executor.memoryOverhead=2048 --conf spark.executor.extraJavaOptions="-XX:MaxPermSize=64m -XX:+CMSClassUnloadingEnabled -XX:MaxDirectMemorySize=1536m -Xmn100m -XX:MaxTenuringThreshold=1 -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=10 -XX:+UseCompressedOops -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintHeapAtGC -XX:+PrintGCApplicationConcurrentTime -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError"

在yarn里面进行跑spark的时候,出现以下问题:

2018-12-24 16:55:31,467 | INFO  | [Executor task launch worker-0] | Finished task 4.0 in stage 15.0 (TID 59). 880 bytes result sent to driver | org.apache.spark.Logging$class.logInfo(Logging.scala:59)
2018-12-24 16:55:31,595 | INFO  | [sparkExecutor-akka.actor.default-dispatcher-4] | Got assigned task 63 | org.apache.spark.Logging$class.logInfo(Logging.scala:59)
2018-12-24 16:55:31,595 | INFO  | [Executor task launch worker-0] | Running task 2.0 in stage 17.0 (TID 63) | org.apache.spark.Logging$class.logInfo(Logging.scala:59)
2018-12-24 16:55:31,596 | INFO  | [Executor task launch worker-0] | Updating epoch to 4 and clearing cache | org.apache.spark.Logging$class.logInfo(Logging.scala:59)
2018-12-24 16:55:31,597 | INFO  | [Executor task launch worker-0] | Started reading broadcast variable 17 | org.apache.spark.Logging$class.logInfo(Logging.scala:59)
2018-12-24 16:55:31,600 | INFO  | [Executor task launch worker-0] | ensureFreeSpace(3847) called with curMem=110955, maxMem=2222739947 | org.apache.spark.Logging$class.logInfo(Logging.scala:59)
2018-12-24 16:55:31,601 | INFO  | [Executor task launch worker-0] | Block broadcast_17_piece0 stored as bytes in memory (estimated size 3.8 KB, free 2.1 GB) | org.apache.spark.Logging$class.logInfo(Logging.scala:59)
2018-12-24 16:55:31,603 | INFO  | [Executor task launch worker-0] | Reading broadcast variable 17 took 6 ms | org.apache.spark.Logging$class.logInfo(Logging.scala:59)
2018-12-24 16:55:31,603 | INFO  | [Executor task launch worker-0] | ensureFreeSpace(7664) called with curMem=114802, maxMem=2222739947 | org.apache.spark.Logging$class.logInfo(Logging.scala:59)
2018-12-24 16:55:31,603 | INFO  | [Executor task launch worker-0] | Block broadcast_17 stored as values in memory (estimated size 7.5 KB, free 2.1 GB) | org.apache.spark.Logging$class.logInfo(Logging.scala:59)
2018-12-24 16:55:31,605 | INFO  | [Executor task launch worker-0] | Beginning offset 252829 is the same as ending offset skipping DXLOANBAL_1 2 | org.apache.spark.streaming.kafka.KafkaRDD.compute(KafkaRDD.scala:88)
2018-12-24 16:55:31,606 | INFO  | [Executor task launch worker-0] | Finished task 2.0 in stage 17.0 (TID 63). 631 bytes result sent to driver | org.apache.spark.Logging$class.logInfo(Logging.scala:59)
2018-12-24 16:55:34,426 | ERROR | [SIGTERM handler] | RECEIVED SIGNAL 15: SIGTERM | org.apache.spark.util.SignalLoggerHandler.handle(SignalLogger.scala:57)
2018-12-24 16:55:34,429 | INFO  | [Thread-1] | Shutdown hook called | org.apache.spark.Logging$class.logInfo(Logging.scala:59)
2018-12-24 16:55:34,433 | ERROR | [sparkExecutor-akka.actor.default-dispatcher-2] | Driver 10.128.65.46:23785 disassociated! Shutting down. | org.apache.spark.Logging$class.logError(Logging.scala:75)
2018-12-24 16:55:34,435 | WARN  | [sparkExecutor-akka.actor.default-dispatcher-4] | Association with remote system [akka.tcp://[email protected]:23785] has failed, address is now gated for [5000] ms. Reason is: [Disassociated]. | akka.event.slf4j.Slf4jLogger$$anonfun$receive$1$$anonfun$applyOrElse$2.apply$mcV$sp(Slf4jLogger.scala:71)
2018-12-24 16:55:34,437 | INFO  | [Thread-1] | Shutdown hook called | org.apache.spark.Logging$class.logInfo(Logging.scala:59)

            这个错误比较隐晦,从信息上看来不知道是什么问题,但是归根结底还是内存的问题,有两个方法可以解决这个错误,

一是,如上面所说,加大excutor-memory的值,减少executor-cores的数量,问题可以解决。

二是,加大executor.overhead的值,但是这样其实并没有解决掉根本的问题。所以如果集群的资源是支持的话,就用1的办法吧。

  另外,这个错误也出现在partitionBy(new HashPartition(partiton-num))时,如果partiton-num太大或者太小的时候会报这种错误,说白了也是内存的原因,不过这个时候增加内存和overhead没有什么用,得去调整这个partiton-num的值。

或者参考

参考链接地址:   http://spark.apache.org/docs/2.2.0/running-on-yarn.html 

猜你喜欢

转载自blog.csdn.net/hexinghua0126/article/details/85252239