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方法,递归删除一级目录及其子目录或子文件。