一、spark内存模型
Spark1.6之前使用的内存模型是StaticMemoryManager实现,1.6之后使用UnifiedMemoryManager实现。
spark支持用参数spark.memory.useLegacyMode来配置使用哪种内存管理模式。
Executor内存使用
(1)RDD存储
当调用RDD的persist或者cache时,RDD的分区会被存储到内存里
(2)shuffle
需要用缓存区存储shuffle的输出和聚合的中间结果
(3)用户代码
编写的代码使用的内存是整个堆除了RDD存储和shuffle之外剩余的空间
1、StaticMemoryManager内存模型
(1)如果spark作业中有较多的RDD持久化操作,则可以将spark.storage.memoryFraction调大点,保证持久化的数据能在内存中容纳,避免内存不够缓存所有数据,导致数据只能写入磁盘,降低性能
(2)如果spark作业shuffle操作较多,则可以将spark.shuffle.memoryFraction调大点,避免shuffle过程中数据过多内存不足溢写到磁盘,降低性能。
(3)如果spark作业频繁GC,task运行的用户代码内存不够,可以适当降低spark.storage.memoryFraction和spark.shuffle.memoryFraction的值。
2、UnifiedMemoryManager内存模型
(1)如果spark作业中有较多的RDD持久化操作,则可以将spark.storage.memoryFraction调大点,保证持久化的数据能在内存中容纳,避免内存不够缓存所有数据,导致数据只能写入磁盘,降低性能
(2)如果spark作业shuffle操作较多,则可以将spark.storage.memoryFraction调低点,避免shuffle过程中数据过多内存不足溢写到磁盘,降低性能。
(3)如果spark作业频繁GC,task运行的用户代码内存不够,可以适当降低spark.storage.memoryFraction的值。
二、jvm内存优化
1、RDD使用序列化后再持久化
垃圾回收的开销和对象个数成正比,所以减少对象个数,可以减少垃圾回收的开销。最好的方式是使用序列化的方式存储数据,这是每个RDD分区只包含一个对象(一个巨大的字节数组)。
spark默认情况下使用60%的空间用于持久化,40%用于task执行代码逻辑,如果出现GC,那就需要调整缓存区的参数,或者使用序列化对象后再存储,减少缓存区大小,进而给task更多的空间,避免频繁的GC
2、GC调优
(1)Minor GC出现多次,Full GC出现次数少,适当的给新生代更多的内存,比如Eden内存为E,设置Young区的内存为4/3*E,
这样Eden变大了,同时survivor区也变大了。然后再观察任务运行情况
(2)Old区接近满时,说明缓存的对象多了,此时可以降低缓存空间,最好的方式时缓存更少的对象,比如使用序列化对象再放入缓存中
3、数据源是HDFS
读取HDFS数据解压后的大小是解压前的2~3倍,需要评估下Eden的内存大小,比如设置成3~4倍的工作内存空间
三、jvm的GC导致shuffle拉取文件失败
spark运行的时候出现shuffle拉取文件失败,如shuffle output file lost,正真的原因是GC导致的。
如果GC是Full GC,通常会暂停用户线程,此时下一个stage会默认重试3次拉取数据,每次重试时间5s,即15秒没有拉取到文件,就会报类似shuffle output file lost的异常。
优化方式
(1)调整shuffle的数据重试次数,默认是3次,适当的调大。
(2)调整shuffle数据重试间隔,默认是5秒,适当的调大,参数:spark.shuffle.io.retrywait