spark从入门到放弃二十二:Spark 性能优化(5)java
文章地址:http://www.haha174.top/article/details/259787
背景
如果在持久化RDD的时候持久化了大量的数据那么java 虚拟机在垃圾回收的时候就可能成为一个性能瓶颈。因为java虚拟机会定期的进行垃圾回收,此时会最总所有的java对象并且在垃圾回收时找到些不在使用的对象进行回收。
垃圾回收的性能开销,是根内存中对象的数量成正比的所以对于垃圾回收的性能问题首先要做的是,使用高效的数据结构,比如array和String 其次在持久化RDD时候。使用序列化持久化级别而且用kyro 这样的序列化类库,这样每个partition就只是一个对象--一个字节数组
gc对性能的影响就在于如果内存中数据比较大的话,那么可能会很频繁就会在成内存空间满了不够用了此时gc就会很频繁的发生那么本身gc 就是有性能的消耗。而且还频繁发生,那么对性能当然有影响啦。
此外如果数据量过大的话,那么每次gc 的时候要回收的是不是也特别多。那么会导致gc 的速度比较慢。除此之外gc 发生的时候,gc 是一个线程那么比如说task 是工作线程gc 运行的时候会让工作线程停下来。让gc单独运行这样就会直接导致了我们task执行的停止,印象spark线程的执行速度,降低spark的性能。
监测:
我们可以对垃圾回收进行监测,包括多久进行一次回收,以每次回收的耗费时间。只要在spark-submit脚本中添加一个配置即可。
--conf "spark.executor.extra.javaOptions=-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamaps"
但是要记住这里虽然会打印java 虚拟机的垃圾回收的相关信息但是输出到了worker的日志上额不是driver 的日志上。
但是这种方式也是一种,其实完全可以通过SparkUI来观察每个stage的垃圾回收的情况
优化executor内存比例
对于垃圾回收来说,最重要的就是调节RDD缓存中占用的内存空间,与算子执行时创建的对象占用的内存空间的比例,默认情况下,spark使用每个executor 60%的内存空间来缓存RDD。那么在task执行期间创建的对象只有40%的空间来存存放。
在这种情况下,很有可能因为你的内存空间不足,task创建的对象过大,那么一旦发现40%的内存空间不够用了,就会触发java虚拟机的垃圾回收操作。因此在极端的情况下垃圾回收可能会频繁的触发。
在上述情况下 ,如果发现垃圾回收频繁的发生没那么就需要对这个比例进行优化。使用
conf.set("spark.storage.memoryFunction","0.5")即可,
可以将RDD缓存占用空间的比例降低从而给更多的task常见的对象进行使用
因此对于RDD的持久化完全可以使用kyro序列化,加上降低其executor内存占比的方式,来减少其内存消耗,给task提供更多的内存,从而避免task的执行频繁的垃圾回收。
垃圾回收调优1
java堆空间被划分成了两块空间,一个是年轻代,一个是老年代。年轻代放的是短时间的存活的对象,老年代放的是长时间的存活对象。年轻代又被划分成了三块空间,Eden,Survivor1,Survivor2.
首先Eden区域和Survivor1区域用于存放对象,Survivor2区域备用。创建的对象,首先放入Eden区域和Survivor1区域,如果Eden区域满了,那么就会触发一次Minor GC,进行年轻代的垃圾回收。Eden和Survivor1区域中存活的对象,会被移动到Survivor2区域中。然后Survivor1和Survivor2的角色调换。Survivor1变成了备用。
如果一个对象,在年轻代,撑过了多次垃圾回收,都没有被回收掉,那么会被认为是长时间存活的,此时就会被移入老年代。此外,如果在将Eden和Survivor1中存活对象,尝试放入Survivor2中时,发现Survivor2放满了,那么会直接放入老年代。此时就出现了,超时间存活的对象,进入老年代的问题。
如果老年代空间满了没那么就会触发full GC进行老年的垃圾回收操作。
垃圾回收调优2
Spark中垃圾回收调优的目标就是,只有真正长时间存活的对象,才能进入老年代,短时间存活的对象,对只能呆在年轻代。不能因为某个Survivor区域空间不够,在Mintor GC时,就进入了老年代。从而造成了短时间存活的对象,长期呆在老年代中占据了空间,而且full GC时要回收大量的短时间存活的对象,导致full GC速度缓慢。
如果发现,在task执行期间,大量full gc 发生了 ,那么说明,年轻代的Survivor区域,给的空间不够大,此时可以执行一些操作来优化垃圾回收行为:
1.包括降低spark.storage.memoryFraction的比例,给年轻代更多的空间,来存放短时间存活的对象;
2.给Eden 区域分配更大的空间,使用-Xmm即可 ,通常建议给Eden 区域,预计大小的4/3;
3.如果使用的是HDFS文件,那么很好估计Eden区域大小,如果executor有4个task.然后每个hdfs压缩块 解压缩后大小是3倍,此外每个hdfs块的大小是64m,那么Eden区域的预计大小就是:4364MB.