Spark源码[3]-MemoryManager模型

每个Executor都会有一个MemoryManager进程,用于管理该Executor中的执行内存和存储内存分配,其代码位于spark-core模块下的
org.apache.spark.memory.MemroyManager,是一个抽象类。

1 MemoryManger种的属性

MemroyManager中会维护四种我们之前介绍过的线程池:


分别是堆内存储/执行内存池和堆外存储/执行内存池,并传入当前类作为lock,并为其配置存储/执行的大小。而堆外内存需要读取一个配置参数spark.memory.offHeap.size,根据其大小进行执行/存储内存的配置。

2 MemoryManager中的待实现方法

2.1 acquireExecutionMemory

  def acquireExecutionMemory(numBytes: Long,taskAttemptId: Long,memoryMode: MemoryMode): Long

在尝试任务执行时候,从堆内或者堆外进行Task执行内存的申请,所需大小为numBytes

2.2 acquireStorageMemory

acquireStorageMemory(blockId: BlockId, numBytes: Long, memoryMode: MemoryMode): Boolean

在尝试任务执行时候,为存储指定Block,从堆内或者堆外进行存储内存的申请,所需大小为numBytes

2.3 acquireUnrollMemory

def acquireUnrollMemory(blockId: BlockId, numBytes: Long, memoryMode: MemoryMode): Boolean

在尝试任务执行时候,从堆内或者堆外进行Task展平内存的申请,所需大小为numBytes。
RDD 在缓存到存储内存之前,Partition 中的数据一般以迭代器(Iterator)的数据结构来访问。通过 Iterator 可以获取分区中每一条序列化或者非序列化的数据(Record),这些 Record 的对象实例在逻辑上占用了 JVM 堆内内存的 other 部分的空间,同一 Partition 的不同 Record 的空间并不连续。

当调用RDD的persist相关方法中包含内存的存储方法时,RDD 在缓存到[存储内存]之后,Partition 被转换成 Block,Record 在堆内或堆外存储内存中占用一块连续的空间。将Partition从other区域由不连续的存储空间转换为Storage区域连续存储空间的过程,Spark称之为"展开"(Unroll)。
Block 有序列化和非序列化两种存储格式,具体以哪种方式取决于该 RDD 的存储级别。非序列化的 Block 以一种 DeserializedMemoryEntry 的数据结构定义,用一个数组存储所有的对象实例;序列化的 Block 则以 SerializedMemoryEntry的数据结构定义,用字节缓冲区(ByteBuffer)来存储二进制数据。每个 Executor 的 Storage 模块用一个链式 Map 结构(LinkedHashMap)来管理堆内和堆外存储内存中所有的 Block 对象的实例,对这个 LinkedHashMap 新增和删除间接记录了内存的申请和释放。

在展开过程中不能保证存储空间可以一次容纳 Iterator 中的所有数据,在 Unroll 时要向 MemoryManager 申请足够的 Unroll 空间来临时占位,如果申请的空间不足存放整个Iterator,则 Unroll 失败;空间足够时可以继续进行展开。对于序列化的 Partition,其所需的 Unroll 空间可以直接累加计算,一次申请。而非序列化的 Partition 则要在遍历 Record 的过程中依次申请,即每读取一条 Record,采样估算其所需的 Unroll 空间并进行申请,空间不足时可以中断,释放其他Block已占用的不需要的 Unroll 空间。如果最终 Unroll 成功,当前 Partition 所占用的 Unroll 空间被转换为正常的缓存 RDD 的存储空间,如下图所示。


在静态内存管理时,Spark 在存储内存中专门划分了一块 Unroll 空间,其大小是固定的;而统一内存管理时则没有对 Unroll 空间进行特别区分,使用的是Storage存储空间。当存储空间不足时会根据动态占用机制进行处理。

2.4 releaseExecutionMemory

def releaseExecutionMemory(
    numBytes: Long,
    taskAttemptId: Long,
    memoryMode: MemoryMode): Unit = synchronized {
  memoryMode match {
    case MemoryMode.ON_HEAP => onHeapExecutionMemoryPool.releaseMemory(numBytes, taskAttemptId)
    case MemoryMode.OFF_HEAP => offHeapExecutionMemoryPool.releaseMemory(numBytes, taskAttemptId)
  }
}

释放Executor的内存。

2.5 releaseAllExecutionMemoryForTask

private[memory] def releaseAllExecutionMemoryForTask(taskAttemptId: Long): Long = synchronized {
  onHeapExecutionMemoryPool.releaseAllMemoryForTask(taskAttemptId) +
    offHeapExecutionMemoryPool.releaseAllMemoryForTask(taskAttemptId)
}

释放目标Task的所有内存。

3 MemoryManger的具体实现->UnifiedMemoryManager

截止到Spark2.4,已经将静态内存管理完全移除,上述的MemoryManager只有一个实现类,那就是UnifiedMemoryManager,动态内存管理。存储内存和执行内存之间有软边界,可以彼此借内存使用。

3.1 UnifiedMemoryManager包含的属性

maxHeapMemory:最大的堆内存为(ExecutorMemory-300MB)*0.6,该比例通过spark.memory.fraction进行设置
onHeapStorageRegionSize:存储内存的堆内存大小
numCores:CPU内核数

3.2 UnifiedMemoryManager实现的方法

3.2.1 maxOnHeapStorageMemory

返回用于存储的最大堆内存,由于存储内存和执行内存之间的软边界,所以在某一时间点可用的存储内存是堆内存-已使用的执行内存,也就是说相当于存储内存+执行内存未使用的。而最大执行内存计算方式也相同。


3.2.2 maxOffHeapStorageMemory

返回用于存储的最大堆外内存:


3.2.3 acquireExecutionMemory

申请执行内存最终会调用执行内存池种的方法:


先确认使用哪种内存模式,然后分别定义maybeGrowExecutionPool和computeMaxExecutionPoolSize两个方法:
maybeGrowExecutionPool表示如果传入的参数(当前申请不足的内存大小)大于0,则尝试进行对存储内存占用执行内存的部分进行驱逐,如果还不够,那么需要进一步驱逐存储内存的Block。
computeMaxExecutionPoolSize用于计算当前情况下,执行内存最多可以使用的内存。如果存储内存占用不足其配置大小,则将未占用部分也给执行内存使用。而如果占用的超过了配置大小,则将多余占用的部分会强制回收,也就是将执行内存配置大小作为可用大小。最终会调用执行内存池进行内存申请。

3.2.4 acquireStorageMemory

先确认使用哪种内存模式,如果内存不足,那么先尝试对执行内存的借用,然后调用存储内存池的方法进行内存申请,注意返回的是Boolean,是否申请成功,如果不够,那么直接返回false。

3.2.5 acquireUnrollMemory

调用的是acquireStorageMemory,也就明确了,展开Block操作其实占用的是存储内存。存储内存的用途就变成了:存储Block,进行Block展开。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Spark 作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统中扮演着非常重要的角色。理解 Spark 内...
    Alukar阅读 1,005评论 0 7
  • spark内存使用大小管理 MemoryManager 的具体实现上,Spark 1.6 之后默认为统一管理(Un...
    曾小俊爱睡觉阅读 555评论 0 1
  • 正文内容分为上下两篇来阐述,上一篇见《Spark内存管理详解(上)——内存分配》[https://www.jian...
    LeonLu阅读 4,677评论 5 32
  • 在这行Spark的应用程序时,Spark集群会启动Driver和Executor两种JVM线程,前者为主控进程,负...
    Sol__C阅读 1,642评论 0 1
  • 一个人如果能有他珍爱的人或物陪伴在身边,那他一定是幸福的! 我不是一个太感伤的人,不会因为一些无关痛痒的琐事烦扰到...
    单车吉他浪迹天涯阅读 231评论 1 1