CC00105.spark——|Hadoop&Spark.V03|——|Spark.v03|Spark 原理 源码|内存管理&存储内存管理|

本文深入探讨了Spark的存储内存管理和RDD持久化机制。内容包括堆内、堆外内存的区别,RDD的持久化原理,以及如何通过persist或cache方法提升计算速度。详细阐述了BlockManager的角色,Block的存储级别,以及存储级别的不同组合。同时,解释了RDD的缓存过程,涉及内存中的Iterator转换为连续存储的Block,并讨论了淘汰与落盘的策略。
摘要由CSDN通过智能技术生成
一、存储内存管理
### --- 存储内存管理

~~~     堆内内存:系统保留(300M)、Other、存储内存、执行内存
~~~     堆外内存:存储内存、执行内存
~~~     存储内存:RDD缓存的数据 & 共享变量
~~~     RDD的持久化
~~~     RDD缓存的过程
~~~     淘汰与落盘
二、RDD 持久化机制
### --- RDD持久化机制

~~~     RDD作为 Spark 最根本的数据抽象,是只读的分区记录的集合,
~~~     只能基于在稳定物理存储中的数据集上创建,
~~~     或者在其他已有的 RDD 上执行转换操作产生一个新的 RDD。
~~~     转换后的 RDD 与原始的 RDD 之间产生的依赖关系。
~~~     凭借Lineage,Spark保证了每一个 RDD 都可以被重新恢复。
~~~     但 RDD 的所有转换都是惰性的,即只有当一个返回结果给
~~~     Driver 的Action发生时,Spark 才会创建任务读取 RDD,然后真正触发转换的执行。
### --- Task 在启动之初读取一个分区时:

~~~     先判断这个分区是否已经被持久化
~~~     如果没有则需要检查 Checkpoint 或按照血统重新计算。
~~~     如果一个 RDD 上要执行多次Action,可以在第一次行动中使用 persist 或 cache 方法,
~~~     在内存或磁盘中持久化或缓存这个 RDD,从而在执后面的Action时提升计算速度。
~~~     RDD 的持久化由 Spark 的 Storage【BlockManager】 模块负责,实现了 RDD 与物理存储的解耦合。
~~~     Storage 模块负责管理 Spark 在计算过程中产生的数据,将那些在内存或磁盘、
~~~     在本地或远程存取数据的功能封装了起来。
~~~     在具体实现时Driver 端和 Executor 端 的 Storage 模块构成了主从式架构,
~~~     即 Driver 端 的 BlockManager 为Master,Executor 端的 BlockManager 为 Slave。
~~~     Storage 模块在逻辑上以 Block 为基本存储单位,
~~~     RDD 的每个Partition 经过处理后唯一对应一个Block。
~~~     Driver 端的 Master 负责整个 Spark 应用程序的 Block 的元数据信息的管理和维护,
~~~     而 Executor 端的 Slave 需要将 Block 的更新等状态上报到 Master,同时接收Master 的命令,
~~~     如新增或删除一个 RDD。
~~~     在对 RDD 持久化时,Spark 规定了 MEMORY_ONLY、MEMORY_AND_DISK 等存储级别 ,
~~~     这些存储级别是以下 5个变量的组合:
### --- 源码提取说明

~~~     # 源码提取说明:StorageLevel.scala
~~~     # 39行~45行
class StorageLevel private(
    private var _useDisk: Boolean,
    private var _useMemory: Boolean,
    private var _useOffHeap: Boolean,
    private var _deserialized: Boolean,
    private var _replication: Int = 1)
  extends Externalizable {
### --- Spark中存储级别如下:

~~~     # 源码提取说明:StorageLevel.scala
~~~     # 152行~164行

object StorageLevel {
  val NONE = new StorageLevel(false, false, false, false)
  val DISK_ONLY = new StorageLevel(true, false, false, false)
  val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
  val MEMORY_ONLY = new StorageLevel(false, true, false, true)
  val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
  val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
  val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
  val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
  val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
  val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
  val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
  val OFF_HEAP = new StorageLevel(true, true, true, false, 1)
### --- 存储级别从三个维度定义了 RDD Partition 的存储方式:

~~~     存储位置:磁盘/堆内内存/堆外内存
~~~     存储形式:序列化方式 / 反序列化方式
~~~     副本数量:1份 / 2份
三、RDD缓存过程
### --- RDD 缓存过程
~~~     RDD缓存的源头:Other (Iterator / 内存空间不连续)
~~~     RDD缓存的目的地:存储内存(内存空间连续)

File => RDD1 => RDD2 =====> RDD3 => RDD4 =====> RDD5 => Action
### --- RDD缓存过程

~~~     RDD 在缓存到存储内存之前,Partition 中的数据一般以迭代器(Iterator)的数据结构来访问,
~~~     这是 Scala 语言中一种遍历数据集合的方法。
~~~     通过 Iterator 可以获取分区中每一条序列化或者非序列化的数据项(Record),
~~~     这些 Record的对象实例在逻辑上占用了 JVM 堆内内存的 other 部分的空间,
~~~     同一 Partition 的不同 Record 的存储空间并不连续。
~~~     RDD 在缓存到存储内存之后,Partition 被转换成 Block,
~~~     Record 在堆内或堆外存储内存中占用一块连续的空间。
~~~     将Partition 由不连续的存储空间转换为连续存储空间的过程,Spark 称之为展开(Unroll)。
### --- Block 有序列化和非序列化两种存储格式,具体以哪种方式取决于该 RDD 的存储级别:

~~~     非序列化的 Block 以 DeserializedMemoryEntry 的数据结构定义,用一个数组存储所有的对象实例
~~~     序列化的 Block 以 SerializedMemoryEntry 的数据结构定义,
~~~     用字节缓冲区(ByteBuffer)存储二进制数据
### --- 源码提取说明

~~~     # 源码提取说明:MemoryStore.scala
~~~     # 42行~58行
private sealed trait MemoryEntry[T] {
  def size: Long
  def memoryMode: MemoryMode
  def classTag: ClassTag[T]
}
private case class DeserializedMemoryEntry[T](
    value: Array[T],
    size: Long,
    classTag: ClassTag[T]) extends MemoryEntry[T] {
  val memoryMode: MemoryMode = MemoryMode.ON_HEAP
}
private case class SerializedMemoryEntry[T](
    buffer: ChunkedByteBuffer,
    memoryMode: MemoryMode,
    classTag: ClassTag[T]) extends MemoryEntry[T] {
  def size: Long = buffer.size
}
### --- Executor的storage模块

~~~     每个 Executor 的 Storage 模块用
~~~     LinkedHashMap 来管理堆内和堆外存储内存中所有的 Block 对象的实例,
~~~     对这个HashMap 新增和删除间接记录了内存的申请和释放。
~~~     # 源码提取说明:MemoryStore.scala
~~~     # 81行~92行

private[spark] class MemoryStore(
    conf: SparkConf,
    blockInfoManager: BlockInfoManager,
    serializerManager: SerializerManager,
    memoryManager: MemoryManager,
    blockEvictionHandler: BlockEvictionHandler)
  extends Logging {

  // Note: all changes to memory allocations, notably putting blocks, evicting blocks, and
  // acquiring or releasing unroll memory, must be synchronized on `memoryManager`!

  private val entries = new LinkedHashMap[BlockId, MemoryEntry[_]](32, 0.75f, true)
### --- blockmanager

~~~     备注:MemoryStroe => BlockManager
~~~     因为不能保证存储空间可以一次容纳 Iterator 中的所有数据,
~~~     当前的计算任务在 Unroll 时要向 MemoryManager 申请足够的 Unroll 空间来临时占位,
~~~     空间不足则 Unroll 失败,空间足够时可以继续进行。
~~~     序列化的 Partition,其所需的 Unroll 空间可以直接累加计算,一次申请
~~~     非序列化的 Partition 则要在遍历 Record 的过程中依次申请,即每读取一条 Record,
~~~     采样估算其所需的Unroll 空间并进行申请,空间不足时可以中断,释放已占用的 Unroll 空间
~~~     如果最终 Unroll 成功,当前 Partition 所占用的 Unroll 空间被转换为正常的缓存 RDD 的存储空间
### --- Storage

~~~     在静态内存管理时,Spark 在存储内存中专门划分了一块 Unroll 空间,其大小是固定的,
~~~     统一内存管理时则没有对Unroll 空间进行特别区分,当存储空间不足时会根据动态占用机制进行处理。
四、淘汰与落盘
### --- 淘汰与落盘

~~~     由于同一个 Executor 的所有的计算任务共享有限的存储内存空间,
~~~     当有新的 Block 需要缓存但是剩余空间不足且无法动态占用时,
~~~     就要对 LinkedHashMap 中的旧 Block 进行淘汰(Eviction),
~~~     而被淘汰的 Block 如果其存储级别中同时包含存储到磁盘的要求,
~~~     则要对其进行落盘(Drop),否则直接删除该 Block。
~~~     # 淘汰:从内存空间中清除
~~~     # 落盘:将存储内存中的数据(RDD缓存的数据)写到磁盘上

Memory_And_Disk => cache => Memory
### --- 存储内存的淘汰规则为:

~~~     被淘汰的旧 Block 要与新 Block 的 MemoryMode 相同,即同属于堆外或堆内内存
~~~     新旧 Block 不能属于同一个 RDD,避免循环淘汰
~~~     旧 Block 所属 RDD 不能处于被读状态,避免引发一致性问题
~~~     遍历 LinkedHashMap 中 Block,按照最近最少使用(LRU)的顺序淘汰,
~~~     直到满足新 Block 所需的空间。其中LRU 是 LinkedHashMap 的特性。
~~~     落盘的流程则比较简单,如果其存储级别符合_useDisk 为 true 的条件,
~~~     再根据其 _deserialized 判断是否是非序列化的形式,若是则对其进行序列化,
~~~     最后将数据存储到磁盘,在 Storage 模块中更新其信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yanqi_vip

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值