写点什么

Spark 统一内存划分

作者:矛始
  • 2022 年 7 月 26 日
  • 本文字数:1929 字

    阅读完需:约 6 分钟

1. Executor 内存逻辑架构


  1. 堆内存,由 JVM 分配和回收,由 spark.executor.memory 控制大小,JVM 中序列化的对象是以字节流形式,其占用内存大小可直接计算,对于非序列化对象,其占用的内存是通过周期性地采样近似估算,且被 spark 标记为释放的对象实例也有可能并没有被 JVM 回收,所以 spark 并不能准确记录实际可用堆内存,也就无法避免内存溢出

  2. 非堆内存,不受 JVM 管理,有两部分,其中一部分通常是 yarn 模式中通过 spark.executor.memoryOverhead 配置,该部分内存用于虚拟机自身的开销(字符串、NIO 和其它一些本地开销);另一部分通过 spark.memory.offHeap.enable/size 结合配置,该部分由 spark 直接使用于存储内存和任务内存,从 2.0 开始不再依赖第三方内存系统 Tachyon,而是基于 JDK 自带的 Unsafe API 实现堆外内存管理,堆外内存可以精确地申请和释放,减少了不必要的额外开销。

  3. 系统内存(systemMemory):这里指的是 JVM 可用的最大内存,可通过 Runtime.getRuntime.maxMemory 获得该值,系统内存并不等于分配的堆内存,由于年轻代 GC 采用复制算法,所以有一块 survivor 内存区需要保留,即systemMemory=堆内存-survivor

  4. 可用内存(usableMemory):这部分内存是用户代码能直接影响到的,可用内存=系统内存 - Reserved,其中 Reverved 为固定 300M 的保留内存,用于 spark 系统内部使用。

  5. 应用内存:主要用于存储用户代码生成的数据对象,这些数据对象被缓存之前就是处于应用内存空间

  6. 存储内存与执行内存:存储内存用于缓存数据,执行内存主要用于满足 Shuffle、 Join、 Sort、 Aggregation 等计算过程中对内存的需求,通过 spark.memory.storageFraction 控制两者比例,默认平分,两部分内存之间还可以进行动态占用:

  7. 执行内存的空间被对方占用后,可让对方将占用的部分转存到硬盘,然后归还借用空间

  8. 存储内存空间被对应占用后,无法让对方归还,因为 shuffle 过程中的很多因素无法实现

2. Executor 界面内存计算

在 spark 任务监控界面的 Executors 菜单中有一列“Storage Memory”显示当前“已用/总可用存储内存”情况,当使用静态内存机制时,此列总大小确实为存储内存,但使用统一内存机制时:


显示总内存 = 存储内存+执行内存


虽然两部分内存可以动态占用,但应该总不会把执行内存全部借光。此处分析统一内存该值是怎么个计算方式,既然知道显示的总内存是指存储内存和执行内存总和,那么结合逻辑架构图可以直观地看到组成方式:


显示总内存 =(堆内存储内存+堆内执行内存)+(堆外存储内存+堆外执行内存)            =(可用内存-应用内存)+ offHeap       		 = 可用内存 * spark.memory.fraction + offHeap           =(系统内存-300M)* 0.6 + offHeap           =(spark.executor.memory – survivor – 300M)* 0.6 + offHeap           =(Runtime.getRuntime.maxMemory – 300M)* 0.6 + offHeap
复制代码


这公式中 survivor 内存大小是核心,如果确定了就能确定总内存大小。按理解,可以通过 NewRatio 和 SurvivorRatio 计算出 survivor 大小的,但是通常指定了最大堆内存,但是 jvm 并不会初始化时就会申请到最大内存,则是动态增加的,所以 survivor 大小也只是一个估计值(约为 90%)。可以通过指定 Xms 等于最大堆内存或者禁用 UseAdaptiveSizePolicy,这样 survivor 就可以按比例计算出来。另外需要注意一点,页面是通过请求”allexecutors”接口返回的数据,总内存对应的返回字段是 maxMemory,单位是字节,而展现为 GB 时直接除 1000 而非 1024,且最终结果小数位是五舍四入。

3. UnrollMemory 理解

spark 的 rdd 在缓存到存储内存之前,每条数据的对象实例都处于 JVM 进堆内内存的应用内存,即便同一个分区内的数据在内存空间也不是连续的(更可能不在同一物理节点?),具体分布由 JVM 管理,上层通过 scala 中的迭代器来访问。当 rdd 持久化储存内存之后,partition 对应转换为 block,此时数据在存储内存空间(堆内或堆外)中将连续的存储,这里将分区由不连续的存储空间转换为连续的存储空间的过程,就是 unroll 操作


在静态内存机制中,内存的构成部分有一个叫 unroll 内存,该部分从存储内存中独立划分 约占 20%。但是在统一内存机制中,unroll 内存却不存在了,其实在 spark 抽象的内存管理器(MemoryManger)中抽象了三个方法:


acquireStorageMemoryacquireUnrollMemoryacquireExecutionMemory
复制代码


无论是统一内存(UnifiedMemoryManager)还是静态内存(StaticMemoryManager)按自己的逻辑实现了 3 个方法,统一内存中已经把存储内存和 unroll 内存合并,代码实现层面上UnifiedMemoryManager.acquireUnrollMemory其实也只是简单地调用了一下 acquireStorageMemory

4. 参考

《spark sql 内核剖析》https://zhuanlan.zhihu.com/p/115888408https://www.jianshu.com/p/87a36488993ahttps://blog.csdn.net/lemonZhaoTao/article/details/81990408

发布于: 12 小时前阅读数: 24
用户头像

矛始

关注

还未添加个人签名 2018.12.26 加入

好记性+烂笔头

评论

发布
暂无评论
Spark统一内存划分_spark_矛始_InfoQ写作社区