pyspark 内存不够让其溢出到磁盘是啥参数_spark


前言

注意:本文具有一定的深度,需要您之前对spark的运行流程熟悉,否则,您可能看不懂

不过,也没关系,我会把spark的运行流程重新写一遍,然后再引入进来内存调优。

spark任务运行流程

为了让我的讲述更具有实际性,不是在一个天空中飞翔的感觉,或者说特别空灵的感觉,让我以一段代码作为开始,然后结合代码的方式去解释spark任务的运行流程。


package com.cn.spark.user

import org.apache.spark.{SparkConf, SparkContext}

object Tests {

	def main(args: Array[String]): Unit = {
		val conf = new SparkConf()
		val sc = new SparkContext(conf)
		
		val rdd1 = sc.textFile("hdfs://node1:9000/hello.txt")
		rdd1.map(x=>(x,x*10)).reduceByKey((x,y)=>x+y).collect()
	}

}


相信大家对这段spark代码一点都不会陌生。这个代码很简单,但是他是一个完整的spark任务,我们认定他为一个application。

首先我们先来会想一下,spark有几种集群模式?分别有standalone,spark on Yarn,messs等,但是无论哪种,总体来说,只有两种,一种是客户端模式,一种是集群模式,但是无论是客户端模式还是集群模式,我们都认为提交这段代码的时候选定了一个driver端。

好,我们再来点基础的,当spark启动的时候就会启动一个master,然后和一些work。

再返回driver端,也就是执行到下面代码的时候


val sc = new SparkContext(conf)


这个我们认为就是初始化了一个会话,然后上面的Application就会按照action算子来划分多个job任务,他主要启动了两个schedule,一个DAGschedule,一个taskschedule。

再来说说DAGschedule干了点啥事情,他会根据宽依赖来划分stage,也就是根据shuffle类的算子来划分多个stage,stage内存也会根据分区划分task。

这个时候driver端就会向Master进行注册,然后master开始寻找worker,worker就开始初始化Executor,当然来Executor内部也会初始化线程池。等每个worker初始化Exector之后就会反向向driver端注册,告诉driver端这些任务在哪些服务器上面运行task。

运行到这里的时候,我们的上面的代码就运行完成了,也就是说,上面了一行代码,我们做了上面一系列的事情。

然后就会将每个task发送到相应的worker中的Executor中去执行。

等所有的task执行完毕,我们整个spark任务就执行完毕了。

那我们知道了这些有啥用呢?

接下来进行一个深入的分析,就是Exector这里。

详解Executor内存模型

会想我们在运行spark代码的时候我们指定了这样一个参数:executor-memory

这个参数用与设置每个Executor进程的内存,是的,到这里就到了我们的本文章的重点。

Executor内存分为静态内存模型和统一内存模型。

先来说静态内存模型。

在spark1.6版本之前就是用的静态内存模型。

静态模型就是把一个Executor分成三个部分,一部分是Storage内存区域,一部分是Execution区域,还有一部分是其他区域。在spark的configuration中默认的有以下参数控制。

spark.storage.memoryFraction: 默认0.6

spark.shuffle.memoryFraction: 默认0.2

那么storage内存模型这一部分主要是处理cache操作和广播变量这些。

Execution就是执行map,join,reduce等这些任务的。

那么我们为啥要知道这个呢?就是我们想一个事情,如果假如我们的代码中有shuffle操作,我们都是shuffle,而且其中有一个key的值非常大,我们就需要Execution的内存要大,但是不要意思,只给他分配了0.2,这个时候会出现什么问题呢?毫无疑问,在数据量大于0.2的Execution的时候,一定会出现内存溢出的问题。你可能想,storage内存那么大,我能不能用一些呢?不好意思,不可以,这就是静态模型,也就是静态模型中,storage的内存部分和Execution内存部分不能相互借用,所以这里就是优化点,也就是假如我们运行的代码在spark1.6版本之前的话,我们运行之前就要先想好到底是storage需要的多还是Execution多,我们手动的指定,读到这里,你是不是觉得非常不方便?是的,非常麻烦。于是我们就引入我们的统一内存模型。

在spark2.0版本之后,spark新增加一种模型,就是统一动态模型。

统一动态模型先预留300M内存,然后把Executor内存整体划分为两个部分,由这个spark.memory.fraction这个指定默认值是0.6,另外一部分是0.4,然后spark.memory.fraction这部分又划分两个小部分,这两个小部分整体占用0.6,这两份其实就是storage内存和execution内存。上面我们说静态模型的时候,提到storage和execution不能互相借用,但是在这里他们可以相互借用。

资源调优

经过上面漫长的过程,我们终于理清了逻辑,接下来就让我们用上面学到的知识来优化我们的spark。

num-executors

该参数用户设置spark作业总共需要多少个Exector进行来执行,这个参数很重要,如果不设置,默认只会启动少量的Executor,此时spark作业运行非常的慢。如果您的集群在1000个节点左右,那么建议设置为50~100个左右的Executor进行比较好,如果是100个节点,那么建议设置20~30个左右比较好,既不要设置的太多影响其他的任务,也不要设置的过少影响任务运行速度,需要根据节点来配置。

executor-memory

该参数用于设置每个Executor进程的内存,Executor内存的大小,直接决定spark作业的性能,而且跟常见的jvm 内存溢出有关。所以建议每个Executor进程的内存设置4G~8G较为合适,但是这也只是一个参考值,具体设置还需要根据队列中任务的多少以及最大内存资源来设置,根据经验,内存最好不要超过资源队列的最大内存的1/3~1/2,避免自己的spark作业占用来所有的资源,导致队列中的其他任务无法运行。

executor-cores

该参数用于设置每个Executor进程的cpu core数量,一个cpu core同一时间只能运行一个task,因此每个Executor进程中的cpu core数量越多,越能够快速的执行完分配给自己的所有task任务,这里建议每个Executor的数量设置为2~4个较为合适,同样也要根据不同部门的资源队列来定,同样如果跟别人分享这个队列,那么num-executors*executor-cors不要超过cpu core的1/3~1/2左右较为合适,避免影响队列中其他任务的运行。

driver-memory

这个用来设置driver进程的内存。一般这个参数我们是不设置的,但是除非用collect算子需要设置。

spark.default.parallelism

该参数用户设置每个stage的默认task数量,这个参数及其重要,如果不设置可能会直接影响你的spark作业性能。来分析以下默认的task数量是有什么导致的?当然是由block块导致的,默认的情况下,一个block对应一个task任务,如果你上面的参数都设置了很多,这里不设置,那么相当于,你申请了资源还不用,这就是耍流氓,所以设置原则为num-executors&executor-cors的2-3倍较为合适。

如果executor 的总cpu core数量为300个,那么设置1000个task是可以的,此时可以充分的利用spark集群的资源。

spark.storage.memoryFraction(Spark1.6之前的参数)

该参数针对spark1.6之前,用于设置静态内存storage部分的内存,默认是0.6。

建议是如果你的spark任务重,需要持久化的内容很多,这个时候可以增加一些内存的比例。

spark.shuffle.memoryFraction(Spark1.6之前的参数)

该参数是针对spark1.6之前,用户这是execution的部分内存,如果您觉的需要计算的比较多,可以增加他的比例。

命令例子

到这里,也就要到了这篇文章的尾声了,上面说了那么多,那么究竟该如何设置呢?下面给出一份例子。


./bin/spark-submit 
  --master yarn-cluster 
  --num-executors 100 
  --executor-memory 6G 
  --executor-cores 4 
  --driver-memory 1G 
  --conf spark.default.parallelism=1000 
  --conf spark.storage.memoryFraction=0.5 
  --conf spark.shuffle.memoryFraction=0.3


最后再来说一下spark在运行的过程中常出现的问题


java.lang.OutOfMemoryError
ExecutorLostFailure
Executor exit code 为143
executor lost
hearbeat time out
shuffle file lost

如果遇到以上问题,很有可能就是内存除了问题,可以先尝试增加内存。


总结

点击关注不迷路哦,如果您有什么想要和我探讨的,可以在下方给我留言,另外关注我,等我有更新,也会推送给您的。