Spark存储体系——内存管理器

Spark与Hadoop的重要区别之一就在于对内存的使用。Hadoop只将内存作为计算资源,Spark除将内存作为计算资源外,还将内存的一部分纳入到存储体系中。Spark使用MemeoryManager对存储体系和内存计算所使用的内存进行管理。

1 内存池模型

内存池实质是对物理内存的逻辑规划,协助Spark任务在运行合理地使用内存资源。Spark将内存从逻辑上区别为堆内存和堆外内存,称为内存模式(MemoryMode)。枚举类型MemoryMode中定义了堆内存和堆外内存,代码如下:

//org.apache.spark.memory.MemoryMode 
@Private
public enum MemoryMode {
  ON_HEAP,
  OFF_HEAP
}

这里的堆内存并不能与JVM中的Java堆内存直接画等号,它只是JVM堆内存的一部分。堆外内存则是Spark使用 sun.misc.Unsafe 的API 直接在工作节点的系统内存中开辟的空间。无论是哪种内存,都需要一个内存池对内存进行资源管理,抽象类 MemoryPool 定义了内存池的规范

//org.apache.spark.memory.MemoryPool
private[memory] abstract class MemoryPool(lock: Object) {
  @GuardedBy("lock")
  private[this] var _poolSize: Long = 0
  final def poolSize: Long = lock.synchronized {
    _poolSize
  }
  final def memoryFree: Long = lock.synchronized {
    _poolSize - memoryUsed
  }
  final def incrementPoolSize(delta: Long): Unit = lock.synchronized {
    require(delta >= 0)
    _poolSize += delta
  }
  final def decrementPoolSize(delta: Long): Unit = lock.synchronized {
    require(delta >= 0)
    require(delta <= _poolSize)
    require(_poolSize - delta >= memoryUsed)
    _poolSize -= delta
  }
  def memoryUsed: Long
}

内存池的基本属性如下:

  • lock:对内存池线程安全保证的锁对象
  • _poolSize:内存池的大小

内存池的基本方法如下:

  • poolSize:返回内存池的大小
  • memoryUsed:获取已经使用的内存大小
  • memoryFree:获取内存池的空闲空间
  • incrementPoolSize:给内存池扩展delta给定的大小
  • decrementPoolSize:将内存池缩小delta给定的大小

Spark中一共有两种MemoryPool的具体实现,分别为StorageMemoryPool和ExecutionMemoryPool。StorageMemoryPool是存储体系用到的内存池,而ExecutionMemoryPool则是计算引擎用到的内存池。

2 StorageMemoryPool详解

Spark既将内存作为存储体系的一部分,又作为计算引擎所需要的计算资源,因此MemoryPool既有用于存储体系的实现类 StorageMemoryPool,又有用于计算的 ExecutionMemoryPool。

 StorageMemoryPool是对用于存储的物理内存的逻辑抽象,通过对存储内存的逻辑管理,提高 Spark 存储体系对内存的使用效率。StorageMemoryPool继承了MemoryPool的lock和_poolSize两个属性,还增加了一些特有的属性:

  • memoryMode:内存模式。由此可以断定用于存储的内存池包括堆内存的内存池和堆外内存的内存池。StorageMemoryPool的堆外内存和堆内存都是逻辑上的区分。
  • poolName:内存池的名称
  • _memoryUsed:已经使用的内存大小
  • _memoryStore:当前StorageMemoryPool所关联的MemoryStore

下面学习一些详细的方法:

2.1 acquireMemory

用于给BlockId对应的Block获取numBytes指定大小的内存

//org.apache.spark.memory.StorageMemoryPool
def acquireMemory(blockId: BlockId, numBytes: Long): Boolean = lock.synchronized {
  val numBytesToFree = math.max(0, numBytes - memoryFree)
  acquireMemory(blockId, numBytes, numBytesToFree)
}

此方法首先计算要申请的内存大小numBytes与空闲空间memoryFree的差值,如果numBytesToFree大于0,则说明需要腾出部分Block所占用的空间,然后调用重载的acquireMemory方法申请获得内存。

//org.apache.spark.memory.StorageMemoryPool
def acquireMemory(
    blockId: BlockId,
    numBytesToAcquire: Long,
    numBytesToFree: Long): Boolean = lock.synchronized {
  assert(numBytesToAcquire >= 0)
  assert(numBytesToFree >= 0)
  assert(memoryUsed <= poolSize)
  if (numBytesToFree > 0) {//腾出numByBytesToFree属性指定大小的空间
    memoryStore.evictBlocksToFreeSpace(Some(blockId), numBytesToFree, memoryMode)
  }
  val enoughMemory = numBytesToAcquire <= memoryFree
  if (enoughMemory) {
    _memoryUsed += numBytesToAcquire //增加已经使用的内存大小
  }
  enoughMemory //返回是否成功获得了用于存储Block的内存空间
}
  • 1)调用 MemoryStore的evictBlocksToFreeSpace方法,腾出numBytesToFree属性指定大小的空间
  • 2)判断内存是否充足
  • 3)如果内存充足,则将numBytesToAcquire增加到_memoryUsed
  • 4)返回布尔值enoughMemory,即是否成功获得了用于存储Block的内存空间

2.2 releaseMemory

用于释放内存

//org.apache.spark.memory.StorageMemoryPool
def releaseMemory(size: Long): Unit = lock.synchronized {
  if (size > _memoryUsed) {
    logWarning(s"Attempted to release $size bytes of storage " +
      s"memory when we only have ${_memoryUsed} bytes")
    _memoryUsed = 0
  } else {
    _memoryUsed -= size
  }
}

2.3 freeSpaceToShrinkPool

用于释放指定大小的空间,缩小内存池的大小

//org.apache.spark.memory.StorageMemoryPool
def freeSpaceToShrinkPool(spaceToFree: Long): Long = lock.synchronized {
  val spaceFreedByReleasingUnusedMemory = math.min(spaceToFree, memoryFree)
  val remainingSpaceToFree = spaceToFree - spaceFreedByReleasingUnusedMemory
  if (remainingSpaceToFree > 0) {
    val spaceFreedByEviction =
      memoryStore.evictBlocksToFreeSpace(None, remainingSpaceToFree, memoryMode)
    spaceFreedByReleasingUnusedMemory + spaceFreedByEviction
  } else {
    spaceFreedByReleasingUnusedMemory
  }
}

3 MemoryManager模型

有了MemoryPool模型和StorageMemoryPool的基础,下面来了解抽象类MemoryManager定义的内存管理器的接口规范。MemoryManager的属性中既有和存储体系相关的,也有和任务执行相关的:

  • conf:即SparkConf
  • numCores:CPU内核数
  • onHeapStorageMemory:用于存储的堆内存大小
  • onHeapExecutionMemory:用于执行计算的堆内存大小
  • onHeapStorageMemoryPool:用于堆内存的存储内存池,大小由onHeapStorageMemory属性指定
  • offHeapStorageMemoryPool:堆外内存的存储内存池
  • onHeapExecutionMemoryPool:堆内存的执行计算内存池,大小由onHeapExecutionMemory属性指定
  • offHeapExecutionMemoryPool:堆外内存的执行计算内存池
  • maxOffHeapMemory:堆外内存的最大值。可以通过spark.memory.offHeap.size 属性指定,默认为0
  • offHeapStorageMemory:用于存储的堆外内存大小。可以通过 spark.memory.storageFraction 属性(默认为0.5)修改存储占用堆外内存的分数大小来影响offHeapStorageMemory的大小。

可以用下图来表示 MemoryManager 管理的 4 块内存池:

由于MemoryManager中提供的方法既有用于存储体系的,也有用于任务计算的,本节只介绍MemoryManager中提供的与存储体系有关的方法,如下:

  • maxOnHeapStorageMemory:返回用于存储的最大堆内存。需子类实现
  • maxOffHeapStorageMemory:返回用于存储的最大堆外内存。需子类实现
  • setMemoryStore:给onHeapStorageMemoryPool 和 offHeapStorageMemoryPool 设置MemoryStore。
//org.apache.spark.memory.MemoryManager
final def setMemoryStore(store: MemoryStore): Unit = synchronized {
  onHeapStorageMemoryPool.setMemoryStore(store)
  offHeapStorageMemoryPool.setMemoryStore(store)
}
  • acquireStorageMemory:为存储BlockId对应的Block,从堆内存或堆外内存获取所需大小(即numBytes)的内存
  • acquireUnrollMemory:为展开BlockId对应的Block,从堆内存或堆外内存获取 所需大小的内存。
def acquireStorageMemory(blockId: BlockId, numBytes: Long, memoryMode: MemoryMode): Boolean
def acquireUnrollMemory(blockId: BlockId, numBytes: Long, memoryMode: MemoryMode): Boolean
  • releaseStorageMemory:从堆内存或堆外内存释放指定大小的内存
def releaseStorageMemory(numBytes: Long, memoryMode: MemoryMode): Unit = synchronized {
  memoryMode match {
    case MemoryMode.ON_HEAP => onHeapStorageMemoryPool.releaseMemory(numBytes)
    case MemoryMode.OFF_HEAP => offHeapStorageMemoryPool.releaseMemory(numBytes)
  }
}
  • releaseAllStorageMemory:从堆内存及堆外内存释放所有内存
final def releaseAllStorageMemory(): Unit = synchronized {
  onHeapStorageMemoryPool.releaseAllMemory()
  offHeapStorageMemoryPool.releaseAllMemory()
}
  • releaseUnrollMemory:释放指定大小的展开内存
final def releaseUnrollMemory(numBytes: Long, memoryMode: MemoryMode): Unit = synchronized {
  releaseStorageMemory(numBytes, memoryMode)
}
  • storageMemoryUsed:onHeapStorageMemoryPool与offHeapStorageMemoryPool中一共占用的存储内存
final def storageMemoryUsed: Long = synchronized {
  onHeapStorageMemoryPool.memoryUsed + offHeapStorageMemoryPool.memoryUsed
}

MemoryManager有两个子类,分别是StaticMemoryManager和UnifiedMemoryManager。StaticMemoryManager保留了早期Spark版本遗留下来的静态内存管理机制,也不存在堆外内存的模型。在静态内存管理机制下,Spark应用程序 在运行的存储内存和执行内存的大小均为固定的。从Spark 1.6.0 版本开始,以 UnifiedMemoryManager 作为默认的内存管理吕。UnifiedMemoryManager提供了统一的内存管理机制,即Spark应用程序在运行期的存储内存和执行内存将共享统一的内存空间,可以动态调节两块内存的空间大小。下文将讲述UnifiedMemoryManager。

4 UnifiedMemoryManager详解

UnifiedMemoryManager在MemoryManager的内存模型之上,将计算内存和存储内存之间的边界修改为“软”边界,即任何一方可以向另一方借用空闲的内存。

UnifiedMemoryManager的成员属性:

  • conf:即SparkConf。此构造器属性将用于父类MemoryManager的构造器属性——conf
  • maxHeapMemory:最大堆内存。大小为系统可用内存与 spark.memory.fraction 属性值(默认为0.6)的乘积
  • onHeadpStorageRegionSize:用于存储的堆内存大小。此构造器将用于父类MemoryManager的构造器属性——onHeapStorageMemory。由于UnifiedMemoryManager的构造器属性中没有 onHeapExecutionMemory,所以 maxHeapMemory与onHeapStorageRegionSize的差值就作为父类MemoryManager的构造器属性——onHeapExecutionMemory
  • numCores:CPU内核数。此构造器属性将用于父类MemoryManager的构造器属性——numCores

UnifiedMemoryManager对内存管理如下图示:

上图中将 onHeapStorageMemoryPool 和 onHeapExecutionMemoryPool 合在一起,将堆内存作为一个整体看待。即存储方或计算的空闲空间(即memoryFree表示的区域)都可以供给另一方使用。

由于UnifiedMemoryManager中既有存储相关的方法,也有执行内存相关的方法,本节只介绍存储相关的方法:

  • maxOnHeapStorageMemory:返回用于存储的最大堆内存
//org.apache.spark.memory.UnifiedMemoryManager
override def maxOnHeapStorageMemory: Long = synchronized {
  maxHeapMemory - onHeapExecutionMemoryPool.memoryUsed
}
  • acquireStorageMemory:为存储BlockId对应的Block,从堆内存或堆外存获取所需大小的内存
override def acquireStorageMemory(
    blockId: BlockId,
    numBytes: Long,
    memoryMode: MemoryMode): Boolean = synchronized {
  assertInvariants()
  assert(numBytes >= 0)
  val (executionPool, storagePool, maxMemory) = memoryMode match {
    case MemoryMode.ON_HEAP => (
      onHeapExecutionMemoryPool,
      onHeapStorageMemoryPool,
      maxOnHeapStorageMemory)
    case MemoryMode.OFF_HEAP => (
      offHeapExecutionMemoryPool,
      offHeapStorageMemoryPool,
      maxOffHeapMemory)
  }
  if (numBytes > maxMemory) {
    // Fail fast if the block simply won't fit
    logInfo(s"Will not store $blockId as the required space ($numBytes bytes) exceeds our " +
      s"memory limit ($maxMemory bytes)")
    return false
  }
  if (numBytes > storagePool.memoryFree) {
    // There is not enough free memory in the storage pool, so try to borrow free memory from
    // the execution pool.
    val memoryBorrowedFromExecution = Math.min(executionPool.memoryFree, numBytes)
    executionPool.decrementPoolSize(memoryBorrowedFromExecution)
    storagePool.incrementPoolSize(memoryBorrowedFromExecution)
  }
  storagePool.acquireMemory(blockId, numBytes)
}

其执行步骤如下:

  • 1)根据内存模式,获取此内存模式的计算内存池、存储内存池和可以存储的最大空间
  • 2)对要获得的存储大小进行校验,即numBytes不能大于可以存储的最大空间
  • 3)如果要获得的存储大小比存储内存池的空闲要大,那么就到计算内存池中去借用空间。借用的空间取numBytes和计算内存池的空间空间的最小值
  • 4)从存储内存池获得存储BlockId对应的Block所需的空间

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值