Spark存储体系——磁盘Block管理器

DiskBlockManager 是存储体系的成员之一,它负责为逻辑的Block与数据写入磁盘的位置之间建立逻辑的映射关系。其属性如下:

  • conf:即SparkConf
  • deleteFilesOnStop:停止DiskBlockManager的时候是否删除本地目录的布尔类型标记。当不指定外部的ShuffleClient(即spark.shuffle.service.enabled属性为false)或者当前实例是 Driver 时,此属性为 true
  • localDirs:本地目录的数组
  • subDirsPerLocalDir:磁盘存储DiskStore的本地子目录的数量,可以通过 spark.diskStore.subDirectories 属性配置,默认为64
  • subDirs:DiskStore的本地子目录的二维数组,即File[localDirs.length][subDirsPerLocalDir]
  • shutdownHook:此属性的作用是在初始化DiskBlockManager时,调用addShutdownHook方法,为DiskBlockManager设置好关闭钩子

1 本地目录结构

localDirs 就 DiskBlockManager 管理的本地目录数组。localDirs 是通过调用 createLocalDirs方法创建的本地目录数组,其实质是调用了Utils工具类的 getConfiguredLocalDirs方法获取本地路径(getConfigguredLocalDirs方法默认获取 spark.local.dir 属性或者系统属性 java.io.tmpdir指定的目录,目录可能有多个),并在每个路径下创建以 blockmgr- 为前缀,UUID为后缀的随机字符串的子目录。

//org.apache.spark.storage.DiskBlockManager
private def createLocalDirs(conf: SparkConf): Array[File] = {
  Utils.getConfiguredLocalDirs(conf).flatMap { rootDir =>
    try {
      val localDir = Utils.createDirectory(rootDir, "blockmgr")
      logInfo(s"Created local directory at $localDir")
      Some(localDir)
    } catch {
      case e: IOException =>
        logError(s"Failed to create local dir in $rootDir. Ignoring this directory.", e)
        None
    }
  }
}

根据对localDirs的剖析,可用下图表示DiskBlockManager管理的文件目录:

图中一级目录名称都采用了简单的示意表示,例如Blockmgr-UUID-0、Blockmgr-UUID-1、Blockmgr-UUID-2,代表每个文件夹名称由Blockmgr- 和 UUID 生成的随机串缓存,且此随机串不相同,这里使用N代表subDirsPerLocalDir属性的大小。每个二级目录下都N个二级目录,有些二级目录下可能暂时还没有Block文件。 

2 DiskBlockManager提供的方法

2.1 getFile(filename: String)

此方法根据指定的文件名获取文件

//org.apache.spark.storage.DiskBlockManager
def getFile(filename: String): File = {
  val hash = Utils.nonNegativeHash(filename)
  val dirId = hash % localDirs.length
  val subDirId = (hash / localDirs.length) % subDirsPerLocalDir
  val subDir = subDirs(dirId).synchronized {
    val old = subDirs(dirId)(subDirId)
    if (old != null) {
      old
    } else {
      val newDir = new File(localDirs(dirId), "%02x".format(subDirId))
      if (!newDir.exists() && !newDir.mkdir()) {
        throw new IOException(s"Failed to create local dir in $newDir.")
      }
      subDirs(dirId)(subDirId) = newDir
      newDir
    }
  }
  • 1)调用Utils工具类的nonNegativeHash方法获取文件名的非负哈希值
  • 2)从localDirs数组中按照取余方式获得选中的一级目录
  • 3)哈希值以一级目录的大小获得商,然后用商数与subDirsPerLocalDir取作获得的余数作为选中的二级目录
  • 4)获取二级目录。如果二级目录不存在,则需要创建二级目录
  • 5)返回二级目录下的文件

2.2 getFile(blockId: BlockId)

此方法根据BlockId获取文件。

def getFile(blockId: BlockId): File = getFile(blockId.name)

该方法实际是以BlockId的 name 为参数,通过调用 getFile(filename: String)方法实现的

2.3 containsBlock

此方法用于检查本地 localDirs 目录中是否包含 BlockId 对应的文件

def containsBlock(blockId: BlockId): Boolean = {
  getFile(blockId.name).exists()
}

2.4 getAllFiles

此方法用于获取本地localDirs目录中的所有文件

def getAllFiles(): Seq[File] = {
  subDirs.flatMap { dir =>
    dir.synchronized {
      dir.clone()
    }
  }.filter(_ != null).flatMap { dir =>
    val files = dir.listFiles()
    if (files != null) files else Seq.empty
  }
}

getAllFiles 为了保证线程安全,采用了同步 + 克隆的方式。

2.5 getAllBlocks

此方法用于获取本地localDirs目录中所有Block的BlockId。

def getAllBlocks(): Seq[BlockId] = {
  getAllFiles().map(f => BlockId(f.getName))
}

2.6 createTempLocalBlock

此方法用于为中间结果创建唯一的BlockId和文件,此文件将用于保存本地 Block 的数据。

def createTempLocalBlock(): (TempLocalBlockId, File) = {
  var blockId = new TempLocalBlockId(UUID.randomUUID())
  while (getFile(blockId).exists()) {
    blockId = new TempLocalBlockId(UUID.randomUUID())
  }
  (blockId, getFile(blockId))
}

2.7 createTempShuffleBlock

此方法创建唯一的BlockId和文件,用来存储Shuffle中间结果(即map任务的输出)

def createTempShuffleBlock(): (TempShuffleBlockId, File) = {
  var blockId = new TempShuffleBlockId(UUID.randomUUID())
  while (getFile(blockId).exists()) {
    blockId = new TempShuffleBlockId(UUID.randomUUID())
  }
  (blockId, getFile(blockId))
}

2.8 stop

此方法用于正常停止DiskBlockManager

private[spark] def stop() {
  // Remove the shutdown hook.  It causes memory leaks if we leave it around.
  try {
    ShutdownHookManager.removeShutdownHook(shutdownHook)
  } catch {
    case e: Exception =>
      logError(s"Exception while removing shutdown hook.", e)
  }
  doStop()
}

实际停止DiskBlockManager的方法为doStop

private def doStop(): Unit = {
  if (deleteFilesOnStop) {
    localDirs.foreach { localDir =>
      if (localDir.isDirectory() && localDir.exists()) {
        try {
          if (!ShutdownHookManager.hasRootAsShutdownDeleteDir(localDir)) {
            Utils.deleteRecursively(localDir)
          }
        } catch {
          case e: Exception =>
            logError(s"Exception while deleting local spark dir: $localDir", e)
        }
      }
    }
  }
}

doStop的主要逻辑是遍历localDirs数组中的一级目录,并调用工具类Utils的deleteRecuresively方法,递归删除一级目录及其子目录或子文件。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值