spark的内存管理

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 of M 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 of R as a fraction of M (default 0.5). R is the storage space within M 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.useLegacyModefalse

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: spark.shuffle.memoryFraction
spark.storage.memoryFraction
spark.storage.unrollFraction

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.wait3sHow 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.nodespark.locality.waitCustomize 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.processspark.locality.waitCustomize the locality wait for process locality. This affects tasks that attempt to access cached data in a particular executor process.
spark.locality.wait.rackspark.locality.waitCustomize the locality wait for rack locality.

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值