spark-submit --master yarn
Executor 几个?core?memory?
--executor-memory 1g
--executor-cores 1
--num-executors 2
对spark内存管理要了解,否则提交作业很有可能出错
http://spark.apache.org/docs/latest/tuning.html#memory-management-overview
内存管理概述
在spark里内存的使用主要分为两大类,execution 和 storage
执行的内存主要是用来shuffles 、joins 、sorts和aggregations
存储内存是指用于在集群中缓存和传播内部数据的内存。
在新版本中,spark存储端和执行端是共享区域的。当执行内存没有被使用的时候,存储能够获取到所有资源。如果执行端内存不足时,存储端能够获取存储端的内存。如果有必要的话,执行端能够剔除存储端的内存,所以执行端是比较强势的一点,但是执行端不会把存储端内存全部抢占,执行端是有借不还的,但是会给存储端留一些内存。
总结就是:执行端会借内存给存储端,但是当存储端需要的时候,就会抢占内存。
Although there are two relevant configurations, the typical user should not need to adjust them as the default values are applicable to most workloads:
spark.memory.fraction
expresses the size ofM
as a fraction of the (JVM heap space - 300MB) (default 0.6). The rest of the space (40%) is reserved for user data structures, internal metadata in Spark, and safeguarding against OOM errors in the case of sparse and unusually large records.
spark.memory.fraction 为 (jvm -300M)* 0.6,剩下的40%用了spark做数据处理:用户数据结构、源数据。这样能够避免OOM的发生
spark.memory.storageFraction
expresses the size ofR
as a fraction ofM
(default 0.5).R
is the storage space withinM
where cached blocks immune to being evicted by execution.
内存管理有一个类:MemoryManager.scala
在SparkEnv.scala中
//在spark1.5及1.5之前,使用的是静态资源管理 //该代码出自 spark2.4.4,所以静态内存管理设置为false,通过判断语句执行统一的内存管理 val useLegacyMemoryManager = conf.getBoolean("spark.memory.useLegacyMode", false) val memoryManager: MemoryManager = if (useLegacyMemoryManager) { new StaticMemoryManager(conf, numUsableCores)//静态资源管理 } else { UnifiedMemoryManager(conf, numUsableCores)//统一的内存管理 }
StaticMemoryManager.scala类,这个是1.5版本及之前的机制
storage和execution是单独的
def this(conf: SparkConf, numCores: Int) { this( conf, StaticMemoryManager.getMaxExecutionMemory(conf),//得到最大的执行内存,例如传入为1000M,得到执行内存160M StaticMemoryManager.getMaxStorageMemory(conf),//得到最大的存储内存,例如传入为1000M,得到执行内存540M numCores) }
/** * Return the total amount of memory available for the execution region, in bytes. */ private def getMaxExecutionMemory(conf: SparkConf): Long = { val systemMaxMemory = conf.getLong("spark.testing.memory", Runtime.getRuntime.maxMemory) if (systemMaxMemory < MIN_MEMORY_BYTES) {//如果系统的内存比最小的执行内存还要小,直接抛出错误 throw new IllegalArgumentException(s"System memory $systemMaxMemory must " + s"be at least $MIN_MEMORY_BYTES. Please increase heap size using the --driver-memory " + s"option or spark.driver.memory in Spark configuration.") } if (conf.contains("spark.executor.memory")) {//如果配置文件包含spark.executor.memory,其实就是spark-submit 中的executor-memory配置大小 val executorMemory = conf.getSizeAsBytes("spark.executor.memory") if (executorMemory < MIN_MEMORY_BYTES) { throw new IllegalArgumentException(s"Executor memory $executorMemory must be at least " + s"$MIN_MEMORY_BYTES. Please increase executor memory using the " + s"--executor-memory option or spark.executor.memory in Spark configuration.") } } val memoryFraction = conf.getDouble("spark.shuffle.memoryFraction", 0.2) //默认0.2,内存占比 val safetyFraction = conf.getDouble("spark.shuffle.safetyFraction", 0.8)//默认0.8,安全内存占比 (systemMaxMemory * memoryFraction * safetyFraction).toLong//系统最大内存 * 内存占比 *安全内存占比,比如放入1000m进来,最后执行的内存为 1000 * 0.2 * 0.8 = 160M }
/** * Return the total amount of memory available for the storage region, in bytes. */ private def getMaxStorageMemory(conf: SparkConf): Long = { val systemMaxMemory = conf.getLong("spark.testing.memory", Runtime.getRuntime.maxMemory) val memoryFraction = conf.getDouble("spark.storage.memoryFraction", 0.6)//0.6给存储用 val safetyFraction = conf.getDouble("spark.storage.safetyFraction", 0.9)//安全系数0.9 (systemMaxMemory * memoryFraction * safetyFraction).toLong//比如放入1000m进来,最后执行的内存为 1000M * 0.6 * 0.9 = 540M }
UnifiedMemoryManager,新版本统一内存管理
存储内存和执行内存是公用的==>里面会有相互借用的场景
def apply(conf: SparkConf, numCores: Int): UnifiedMemoryManager = { val maxMemory = getMaxMemory(conf)//得到最大的内存,最大内存为 (系统内存 - 300) * 0.6 = 420M new UnifiedMemoryManager( conf, maxHeapMemory = maxMemory, onHeapStorageRegionSize = (maxMemory * conf.getDouble("spark.memory.storageFraction", 0.5)).toLong,//最大内存 420M * 0.5 = 210M,剩下的给执行的,为210M numCores = numCores) }
private val RESERVED_SYSTEM_MEMORY_BYTES = 300 * 1024 * 1024
/** * Return the total amount of memory shared between execution and storage, in bytes. * 执行端和存储端是共享的 */ private def getMaxMemory(conf: SparkConf): Long = { val systemMemory = conf.getLong("spark.testing.memory", Runtime.getRuntime.maxMemory) val reservedMemory = conf.getLong("spark.testing.reservedMemory", if (conf.contains("spark.testing")) 0 else RESERVED_SYSTEM_MEMORY_BYTES)//默认值为300M val minSystemMemory = (reservedMemory * 1.5).ceil.toLong if (systemMemory < minSystemMemory) { throw new IllegalArgumentException(s"System memory $systemMemory must " + s"be at least $minSystemMemory. Please increase heap size using the --driver-memory " + s"option or spark.driver.memory in Spark configuration.") } // SPARK-12759 Check executor memory to fail fast if memory is insufficient if (conf.contains("spark.executor.memory")) { val executorMemory = conf.getSizeAsBytes("spark.executor.memory") if (executorMemory < minSystemMemory) { throw new IllegalArgumentException(s"Executor memory $executorMemory must be at least " + s"$minSystemMemory. Please increase executor memory using the " + s"--executor-memory option or spark.executor.memory in Spark configuration.") } } val usableMemory = systemMemory - reservedMemory//1000M - 300M = 700M val memoryFraction = conf.getDouble("spark.memory.fraction", 0.6)//可以使用的内存为700 * 0.6 = 420M (usableMemory * memoryFraction).toLong }
spark.memory.useLegacyMode配置作用:
在1.5及1.5之前,有一种叫做静态资源管理。
spark.memory.useLegacyMode | false | Whether to enable the legacy memory management mode used in Spark 1.5 and before. The legacy mode rigidly partitions the heap space into fixed-size regions, potentially leading to excessive spilling if the application was not tuned. The following deprecated memory fraction configurations are not read unless this is enabled: |
Garbage Collection Tuning
http://spark.apache.org/docs/latest/tuning.html#garbage-collection-tuning
垃圾收集
GC调优第一步骤是收集统计java的信息
然后spark运行的时候,要看这些信息的打印日志,这些日志通常会在worker节点。
GC调优
JAVA HEAP会被划分为两个区域:Young 和 old。
年轻代又被分为三个区域
当你的Eden区域满了的时候,就会有 minor GC。
在Spark中进行GC调优的目标是确保在老一代中只存储长期存在的rds,而年轻一代的大小足以存储短期存在的对象。这将有助于避免整个GCs收集在任务执行期间创建的临时对象。
GC时间在UI能够看到
- 通过收集GC统计信息来检查是否有太多的垃圾收集。如果在任务完成之前多次调用完整的GC,这意味着没有足够的可用内存来执行任务。
- 如果次要收集太多而主要gc不多,那么为Eden分配更多的内存会有所帮助。您可以将Eden的大小设置为高估每个任务所需的内存大小。如果伊甸园的大小被确定为E,那么您可以使用选项-Xmn=4/3*E设置年轻代的大小。(4/3的比例也考虑到了幸存者区域使用的空间。)
- 在打印的GC统计数据中,如果旧版本接近满,通过降低spark.memory.fraction来减少用于缓存的内存量;缓存更少的对象比降低任务执行速度要好。或者,考虑减少年轻一代的规模。这意味着降低-Xmn,如果你把它设为上面的值。如果没有,尝试更改JVM的NewRatio参数的值。许多jvm将其默认为2,这意味着老一代占用了2/3的堆。它应该足够大以至于这个分数超过火花。记忆。分数。
- 尝试使用-XX:+UseG1GC的G1GC垃圾收集器。在垃圾收集成为瓶颈的某些情况下,它可以提高性能。注意,对于较大的执行程序堆大小,使用-XX:G1HeapRegionSize来增加G1区域大小可能很重要
- 例如,如果您的任务正在从HDFS读取数据,则可以使用从HDFS读取的数据块的大小来估计任务所使用的内存量。注意,解压缩块的大小通常是块大小的2到3倍。因此,如果我们希望有3或4个任务的工作空间,并且HDFS块大小为128MB,我们可以估计Eden的大小为4*3*128MB。
- 监视垃圾收集所花费的频率和时间随新设置的变化。
Data Locality
数据本地性
http://spark.apache.org/docs/latest/tuning.html#data-locality
数据本地性是比较理想的情况下,实际执行过程中,代码会发送给executor端,申请到资源后,executor是不会改变的了,这个时候只能是将数据移动到执行端。
如果数据和代码在一起,计算就会非常的快。
数据本地性是代码和数据有多远。
spark对于数据本地性有以下几种机制
数据位置是指数据与处理数据的代码之间的距离。基于数据的当前位置有几个级别的局部性。按从最近到最远的顺序排列:
- PROCESS_LOCAL数据与运行代码位于相同的JVM中。这是最好的地点(这种方式已经cache到数据本地的内存中了)
- NODE_LOCAL数据在同一个节点上。示例可能在同一节点上的HDFS中,也可能在同一节点上的另一个执行器中。这比PROCESS_LOCAL稍微慢一些,因为数据必须在进程之间传递
- 从任何地方访问NO_PREF数据的速度都一样快,并且没有区域性首选项
- RACK_LOCAL数据位于相同的服务器机架上。数据位于同一机架上的不同服务器上,因此需要通过网络发送,通常是通过单个交换机
- 任何数据都在网络的其他地方,不在同一个机架上
spark会把所有task优先调度到最好的位置,如果这种方式是不太可能的。在很多场景下,没有未处理的数据在空闲的executor上。这个时候,spark会降低本地化等级,就是把上述的几种机制从上到下的尝试。有以下可能:1.等待繁忙的cpu空闲下来,把作业启动。 2. 启动一个举例远一点的task,这个时候就需要移动数据了
等待空闲的时间由以下参数控制,默认为3S
spark.locality.wait | 3s | How long to wait to launch a data-local task before giving up and launching it on a less-local node. The same wait will be used to step through multiple locality levels (process-local, node-local, rack-local and then any). It is also possible to customize the waiting time for each level by setting spark.locality.wait.node , etc. You should increase this setting if your tasks are long and see poor locality, but the default usually works well. |
spark.locality.wait.node | spark.locality.wait | Customize the locality wait for node locality. For example, you can set this to 0 to skip node locality and search immediately for rack locality (if your cluster has rack information). |
spark.locality.wait.process | spark.locality.wait | Customize the locality wait for process locality. This affects tasks that attempt to access cached data in a particular executor process. |
spark.locality.wait.rack | spark.locality.wait | Customize the locality wait for rack locality. |