技术实操|Apache Spark 内存管理详解(上篇)

Spark 作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统中扮演着非常重要的角色。理解 Spark 内存管理的基本原理,有助于更好地开发 Spark 应用程序和进行性能调优。本文旨在梳理出 Spark 内存管理的脉络,抛砖引玉,引出读者对这个话题的深入探讨。本文中阐述的原理基于 Spark 2.1 版本,阅读本文需要读者有一定的 Spark 和 Java 基础,了解 RDD、Shuffle、JVM 等相关概念。

在执行 Spark 的应用程序时,Spark 集群会启动 Driver 和 Executor 两种 JVM 进程,前者为主控进程,负责创建 Spark 上下文,提交 Spark 作业(Job),并将作业转化为计算任务(Task),在各个 Executor 进程间协调任务的调度,后者负责在工作节点上执行具体的计算任务,并将结果返回给 Driver,同时为需要持久化的 RDD 提供存储功能。由于 Driver 的内存管理相对来说较为简单,本文主要对 Executor 的内存管理进行分析,下文中的 Spark 内存均特指 Executor 的内存。

1. 堆内和堆外内存规划

作为一个 JVM 进程,Executor 的内存管理建立在 JVM 的内存管理之上,Spark 对 JVM 的堆内(On-heap)空间进行了更为详细的分配,以充分利用内存。同时,Spark 引入了堆外(Off-heap)内存,使之可以直接在工作节点的系统内存中开辟空间,进一步优化了内存的使用。

图 1 . 堆内和堆外内存示意图

1.1 堆内内存

堆内内存的大小,由 Spark 应用程序启动时的 –executor-memory 或 spark.executor.memory 参数配置。Executor 内运行的并发任务共享 JVM 堆内内存,这些任务在缓存 RDD 数据和广播(Broadcast)数据时占用的内存被规划为存储(Storage)内存,而这些任务在执行 Shuffle 时占用的内存被规划为执行(Execution)内存,剩余的部分不做特殊规划,那些 Spark 内部的对象实例,或者用户定义的 Spark 应用程序中的对象实例,均占用剩余的空间。不同的管理模式下,这三部分占用的空间大小各不相同(下面第 2 小节会进行介绍)。

Spark 对堆内内存的管理是一种逻辑上的”规划式”的管理,因为对象实例占用内存的申请和释放都由 JVM 完成,Spark 只能在申请后和释放前记录这些内存,我们来看其具体流程:

  • 申请内存:
  1. Spark 在代码中 new 一个对象实例
  2. JVM 从堆内内存分配空间,创建对象并返回对象引用
  3. Spark 保存该对象的引用,记录该对象占用的内存
  • 释放内存:
  1. Spark 记录该对象释放的内存,删除该对象的引用
  2. 等待 JVM 的垃圾回收机制释放该对象占用的堆内内存

我们知道,JVM 的对象可以以序列化的方式存储,序列化的过程是将对象转换为二进制字节流,本质上可以理解为将非连续空间的链式存储转化为连续空间或块存储,在访问时则需要进行序列化的逆过程——反序列化,将字节流转化为对象,序列化的方式可以节省存储空间,但增加了存储和读取时候的计算开销。

对于 Spark 中序列化的对象,由于是字节流的形式,其占用的内存大小可直接计算,而对于非序列化的对象,其占用的内存是通过周期性地采样近似估算而得,即并不是每次新增的数据项都会计算一次占用的内存大小,这种方法降低了时间开销但是有可能误差较大,导致某一时刻的实际内存有可能远远超出预期。此外,在被 Spark 标记为释放的对象实例,很有可能在实际上并没有被 JVM 回收,导致实际可用的内存小于 Spark 记录的可用内存。所以 Spark 并不能准确记录实际可用的堆内内存,从而也就无法完全避免内存溢出(OOM, Out of Memory)的异常。

虽然不能精准控制堆内内存的申请和释放,但 Spark 通过对存储内存和执行内存各自独立的规划管理,可以决定是否要在存储内存里缓存新的 RDD,以及是否为新的任务分配执行内存,在一定程度上可以提升内存的利用率,减少异常的出现。

1.2 堆外内存

为了进一步优化内存的使用以及提高 Shuffle 时排序的效率,Spark 引入了堆外(Off-heap)内存,使之可以直接在工作节点的系统内存中开辟空间,存储经过序列化的二进制数据。利用 JDK Unsafe API(从 Spark 2.0 开始,在管理堆外的存储内存时不再基于 Tachyon,而是与堆外的执行内存一样,基于 JDK Unsafe API 实现[3]),Spark 可以直接操作系统堆外内存,减少了不必要的内存开销,以及频繁的 GC 扫描和回收,提升了处理性能。堆外内存可以被精确地申请和释放,而且序列化的数据占用的空间可以被精确计算,所以相比堆内内存来说降低了管理的难度,也降低了误差。

在默认情况下堆外内存并不启用,可通过配置 spark.memory.offHeap.enabled 参数启用,并由 spark.memory.offHeap.size 参数设定堆外空间的大小。除了没有 other 空间,堆外内存与堆内内存的划分方式相同,所有运行中的并发任务共享存储内存和执行内存。

1.3 内存管理接口

Spark 为存储内存和执行内存的管理提供了统一的接口——MemoryManager,同一个 Executor 内的任务都调用这个接口的方法来申请或释放内存:

清单 1 . 内存管理接口的主要方法

 //申请存储内存
def acquireStorageMemory(blockId: BlockId, numBytes: Long, memoryMode: MemoryMode): Boolean//申请展开内存
def acquireUnrollMemory(blockId: BlockId, numBytes: Long, memoryMode: MemoryMode): Boolean
//申请执行内存def acquireExecutionMemory(numBytes: Long, taskAttemptId: Long, memoryMode: MemoryMode): Long//释放存储内存
def releaseStorageMemory(numBytes: Long, memoryMode: MemoryMode): Unit
//释放执行内存
def releaseExecutionMemory(numBytes: Long, taskAttemptId: Long, memoryMode: MemoryMode): Unit
//释放展开内存
def releaseUnrollMemory(numBytes: Long, memoryMode: MemoryMode): Unit

我们看到,在调用这些方法时都需要指定其内存模式(MemoryMode),这个参数决定了是在堆内还是堆外完成这次操作。

MemoryManager 的具体实现上,Spark 1.6 之后默认为统一管理(Unified Memory Manager)方式,1.6 之前采用的静态管理(Static Memory Manager)方式仍被保留,可通过配置 spark.memory.useLegacyMode 参数启用。两种方式的区别在于对空间分配的方式,下面的第 2 小节会分别对这两种方式进行介绍。

2 . 内存空间分配

2.1 静态内存管理

在 Spark 最初采用的静态内存管理机制下,存储内存、执行内存和其他内存的大小在 Spark 应用程序运行期间均为固定的,但用户可以应用程序启动前进行配置,堆内内存的分配如图 2 所示:

图 2 . 静态内存管理图示——堆内

可以看到,可用的堆内内存的大小需要按照下面的方式计算:

清单 2 . 可用堆内内存空间

可用的存储内存 = systemMaxMemory * spark.storage.memoryFraction *
spark.storage.safetyFraction
可用的执行内存 = systemMaxMemory * spark.shuffle.memoryFraction *
spark.shuffle.safetyFraction 

其中 systemMaxMemory 取决于当前 JVM 堆内内存的大小,最后可用的执行内存或者存储内存要在此基础上与各自的 memoryFraction 参数和 safetyFraction 参数相乘得出。上述计算公式中的两个 safetyFraction 参数,其意义在于在逻辑上预留出 1-safetyFraction 这么一块保险区域,降低因实际内存超出当前预设范围而导致 OOM 的风险(上文提到,对于非序列化对象的内存采样估算会产生误差)。值得注意的是,这个预留的保险区域仅仅是一种逻辑上的规划,在具体使用时 Spark 并没有区别对待,和”其它内存”一样交给了 JVM 去管理。

堆外的空间分配较为简单,只有存储内存和执行内存,如图 3 所示。可用的执行内存和存储内存占用的空间大小直接由参数 spark.memory.storageFraction 决定,由于堆外内存占用的空间可以被精确计算,所以无需再设定保险区域。

图 3 . 静态内存管理图示——堆外

静态内存管理机制实现起来较为简单,但如果用户不熟悉 Spark 的存储机制,或没有根据具体的数据规模和计算任务或做相应的配置,很容易造成”一半海水,一半火焰”的局面,即存储内存和执行内存中的一方剩余大量的空间,而另一方却早早被占满,不得不淘汰或移出旧的内容以存储新的内容。由于新的内存管理机制的出现,这种方式目前已经很少有开发者使用,出于兼容旧版本的应用程序的目的,Spark 仍然保留了它的实现。

2.2 统一内存管理

Spark 1.6 之后引入的统一内存管理机制,与静态内存管理的区别在于存储内存和执行内存共享同一块空间,可以动态占用对方的空闲区域,如图 4 和图 5 所示

图 4 . 统一内存管理图示——堆内

图 5 . 统一内存管理图示——堆外

其中最重要的优化在于动态占用机制,其规则如下:

  • 设定基本的存储内存和执行内存区域(spark.storage.storageFraction 参数),该设定确定了双方各自拥有的空间的范围
  • 双方的空间都不足时,则存储到硬盘;若己方空间不足而对方空余时,可借用对方的空间;(存储空间不足是指不足以放下一个完整的 Block)
  • 执行内存的空间被对方占用后,可让对方将占用的部分转存到硬盘,然后”归还”借用的空间
  • 存储内存的空间被对方占用后,无法让对方”归还”,因为需要考虑 Shuffle 过程中的很多因素,实现起来较为复杂

图 6 . 动态占用机制图示

凭借统一内存管理机制,Spark 在一定程度上提高了堆内和堆外内存资源的利用率,降低了开发者维护 Spark 内存的难度,但并不意味着开发者可以高枕无忧。譬如,所以如果存储内存的空间太大或者说缓存的数据过多,反而会导致频繁的全量垃圾回收,降低任务执行时的性能,因为缓存的 RDD 数据通常都是长期驻留内存的 。所以要想充分发挥 Spark 的性能,需要开发者进一步了解存储内存和执行内存各自的管理方式和实现原理。

技术实操|Apache Spark 内存管理详解(上篇)相关推荐

  1. Apache Spark 内存管理详解

    原文出处: IBM developerWorks Spark 作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统中扮演着非常重要的角色.理解 Spark 内存管理的基本原理,有助于更好地开发 ...

  2. Spark 内存管理详解(下):内存管理

    本文转自:Spark内存管理详解(下)--内存管理 本文最初由IBM developerWorks中国网站发表,其链接为Apache Spark内存管理详解 在这里,正文内容分为上下两篇来阐述,这是下 ...

  3. 万字最全Spark内存管理详解

    今天和大家介绍Spark的内存模型,干货多多,不要错过奥~ 与数据频繁落盘的Mapreduce引擎不同,Spark是基于内存的分布式计算引擎,其内置强大的内存管理机制,保证数据优先内存处理,并支持数据 ...

  4. spark从入门到精通spark内存管理详解- 堆内堆外内存管理

    前言 Spark作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统中扮演着非常重要的角色.理解Spark内存管理的基本原理,有助于更好地开发Spark应用程序和进行性能调优.本文将详细介绍两部 ...

  5. 小白也能接广告赚钱,实操月入3000+项目详解

    小白也能接广告赚钱,实操月入3000+项目详解 大家好,我是独孤思维.今天是原创日更134篇. 01 微头条接广告 自从上次分享了微头条的搬运项目,很多粉丝朋友已经开始实操了,有的甚至两天都涨粉破百, ...

  6. FreeRTOS笔记(六):五种内存管理详解

    不同的嵌入式系统对于内存分配和时间要求不同.FreeRTSO将内存分配作为移植层的一部分,这样FreeRTOS使用者就可以设用自己的合适的内存分配方法. 当内核需要分配内存时可以调用pvPortMal ...

  7. Spark2.1 内存管理详解

    本文中阐述的原理基于 Spark 2.1 版本,阅读本文需要读者有一定的 Spark 和 Java 基础,了解 RDD.Shuffle.JVM 等相关概念. 在执行 Spark 的应用程序时,Spar ...

  8. Nginx内存管理详解

    目录: 1.Nginx内存管理介绍 2.Nginx内存池的逻辑结构 3.Nginx内存池的基本数据结构 4.内存池基本操作介绍 5.内存池管理源码详解 6.内存池使用源码详解 7.小结 1.Nginx ...

  9. 内存管理模拟程序c语言,C语言 内存管理详解

    本文出自: 伟大的Bill Gates曾经失言: 640K ought to be enough for everybody -Bill Gates 1981. 程序员们经常编写内存管理程序,往往提心 ...

  10. 结合源码看nginx-1.4.0之nginx内存管理详解

    目录 0. 摘要 1. nginx内存结构设计 2. nginx内存数据结构 3. nginx内存管理原理 4. 一个简单的内存模型 5. 小结 6. 参考资料 0. 摘要 内存管理,是指软件运行时对 ...

最新文章

  1. 提供一个基于.NET的加密/解密算法
  2. ORACLE 培训 -相克军
  3. docker删除es数据_Docker的常用命令
  4. Autodesk布道GIS新理念
  5. linux6.6 sudo环境变量,linux sudo环境变量设置
  6. FLASH与ASP.NET通讯[Flash | CS3 | ActionScript | ASP.NET | FluorineFx ]
  7. C++ 编写WebService服务
  8. MATLAB的简单动画制作
  9. 阿里云推送证书验证失败
  10. OpenMMLab全景图
  11. ajax怎么传递list类型参数到后端,ajax传递给后台数组参数方式
  12. 监听电源键的单击或长按事件
  13. 全闪存存储、混合闪存存储
  14. 麒麟990银河麒麟SP1升级补丁(0709、1020)升级到(1228)
  15. C语言和汇编语言函数调用
  16. 给iOS App减肥
  17. 魔兽地图服务器存档修改本地存档,【Jass】Preload存档系统,本地写入文件并读取...
  18. memcached + mysql_memcached+Mysql(主从)
  19. 第三方支付系统--支付流程
  20. github pages 一些需要注意的问题

热门文章

  1. 【第二周】吴恩达团队AI for Medical Diagnosis课程笔记
  2. 非线性最小二乘问题的分析与理解(附高斯牛顿法matlab代码)
  3. 硬盘出现“文件或目录损坏且无法读取”的故障,怎么解决?
  4. SQL中NOW() 函数
  5. 怎么在图片上加水印?
  6. 论文研读 —— 7. Very Deep Convolutional Networks for Large-Scale Image Recognition (3/3)
  7. 用python制作正态分布图_使用python绘制3维正态分布图的方法
  8. 计算机在高分子材料中的应用软件,计算机技术在高分子材料工程中的应用(10页)-原创力文档...
  9. 哪个手机浏览器可以倍速_手机四款浏览器APP的比较
  10. PMP-项目风险管理