一.JVM调优
JVM:
老年代:
存放少量生命周期长的对象,如连接池
年轻代:
Spark task执行算子函数自己创建的大量对象
JVM机制:
对象进入java虚拟机之后会放在eden区域和一个survivor区域,还有一个空闲的survivor区域的是空闲的.Eden区域和一个survivor区域满了之后会触发minor GC(小型垃圾回收)清除不再使用的对象,给后续对象腾地方.
活下来没有被清除的对象,会首先放之前空闲的survivor,但是默认eden、survior1和survivor2的内存占比是8:1:1,如果活下来的对象大于1,一个survivor区域放不下,此时会通过JVM的担保机制,将多余1的对象放入老年代
如果JVM内存不够大,可能会造成频繁年轻代内存溢出,频繁的minor GC会导致短时间内,有些存活的对象,多次垃圾回收都没有回收掉,会导致这种短声明周期(不一定是长期使用的)对象,年龄过大,垃圾回收次数太多还没有回收就跑到老年代.
老年代中,经过上面的不断沉凹,可能会造成内不足,囤积一大堆短声明周期的,本应该在年轻代中的,可能马上就要被回收掉的对象,此时,可能导致老年代频繁的满溢,造成频繁进行Full GC(全局/全面垃圾回收).full GC 是针对数量较少的老年代中对象.Full GC的频率应该很少,因此采取了不复杂,但是很严谨的算法,很耗费性能和时间的垃圾回收算法.FullGC很慢.
内存不充足的时候,出现的问题:
1、频繁minor gc,也会导致频繁spark停止工作
2、老年代囤积大量活跃对象(短生命周期的对象),导致频繁full gc,full gc时间很长,短则数十秒,长则数分钟,甚至数小时。可能导致spark长时间停止工作。
3、严重影响咱们的spark的性能和运行的速度。
JVM调优:
1.降低cache(全部缓存到内存)操作的内存占比
Spark中,堆内存分为两块:
一块用于RDD的cache,persist操作进行RDD的数据缓存
一块用于spark算子函数的运行使用
如果cache不那么紧张,但task算子函数创建的对象过多,内存又不够大,就会导致频繁的minor GC 甚至频繁的Full GC,导致spark频繁的停止工作,严重影响性能.
方案:
通过任务运行界面,查看spark作业的运行统计,可以看到stage的运行情况,包括美国task的运行时间,gc时间等,观察GC很频繁就可以适当调节堆内存块比列.
降低cache操作的内存占比,大不了用persist操作,选择将一部分缓存的RDD数据写入磁盘,或者序列化方式,配合Kryo序列化类,减少RDD缓存的内存占用。降低cache操作内存占比,对应的,算子函数的内存占比就提升了。这个时候,可能就可以减少minor gc的频率,同时减少full gc的频率。对性能的提升是有一定的帮助的。
一句话,让task执行算子函数时,有更多的内存可以使用。
spark.storage.memoryFraction,0.6 -> 0.5 -> 0.4 -> 0.2
2.调节executor堆外内存与连接等待时长
1.executor堆外内存:
shuffle file cannot find,executor、task lost,out of memory(内存溢出)
shuffle output file not found,resubmitting task,executor lost(executor挂了,blockmanager也没有了)
针对上述两种错误就可以考虑调节一下executor的堆外内存。也许就可以避免报错。此外,有时堆外内存调节的比较大的时候,对于性能来说,也会带来一定的提升。
可以调节堆外内存的上限:
--conf spark.yarn.executor.memoryOverhead=2048
spark-submit脚本里面,去用--conf的方式,去添加配置。用new SparkConf().set()这种方式去设置是没有用的!一定要在spark-submit脚本中去设置。
spark.yarn.executor.memoryOverhead(看名字,顾名思义,针对的是基于yarn的提交模式)默认情况下,这个堆外内存上限大概是300M。通常在项目中,真正处理大数据的时候,这里都会出现问题,导致spark作业反复崩溃,无法运行。此时就会去调节这个参数,到至少1G(1024M),甚至说2G、4G。
通常这个参数调节上去以后,就会避免掉某些JVM OOM的异常问题,同时呢,会让整体spark作业的性能,得到较大的提升。
2.executor连接等待时长
Executor1在通过TransferServer拉取其他节点上的Executor2的block manager上的数据时,如果Executor2的task创建的对象特别大,特别多,频繁的造成JVM内存溢出,正在进行垃圾回收,所有的工做线程停止,无法提供给Executor1相应,无法建立网络连接,spark默认的网络连接的超时时长,是60s,如果卡住60s都无法建立连接的话,那么就宣告失败了。数据获取不到,可能会导致spark作业崩溃,导致DAGScheduler反复提交stage,TaskScheduler反复提交 task,大大延长了我们的spark作业时间.
方案:调节连接超时时长:
--conf spark.core.connection.ack.wait.timeout=300
spark-submit脚本,切记,不是在new SparkConf().set()这种方式来设置的。
spark.core.connection.ack.wait.timeout(spark core,connection,连接,ack,wait timeout,建立不上连接的时候,超时等待时长)调节这个值比较大以后,通常来说,可以避免部分的偶尔出现的某某文件拉取失败,某某文件lost掉了。