简介
DiskBlockManager主要用来创建并持有逻辑blocks块与物理磁盘位置之间的映射。逻辑block块通过其BlockId映射到一个磁盘上的文件。Block块文件被hash存储到spark.local.dir
(或SPARK_LOCAL_DIRS
)配置的列表目录中。
创建
BlockManager初始化时会创建DiskBlockManager:
val diskBlockManager = {
// Only perform cleanup if an external service is not serving our shuffle files.
val deleteFilesOnStop =
!externalShuffleServiceEnabled || executorId == SparkContext.DRIVER_IDENTIFIER
new DiskBlockManager(conf, deleteFilesOnStop)
}
主要成员
localDirs: Array[File]
:创建spark.local.dir
配置指定的目录列表。然后在这些目录下创建用来存放中间数据的子目录(为了避免顶层目录的inodes过大),最后会根据文件名将其hash存放到不同的目录下。subDirsPerLocalDir
:localDirs代表的各个目录下的子目录的个数,由spark.diskStore.subDirectories
指定,默认为64个。subDirs: Array[Array[File]]
:localDirs代表的各个目录下的子目录,子目录个数由subDirsPerLocalDir
指定。subDirs是不可变的,但是其每个成员subDirs(i)
是可变的,并且subDirs(i)
都是被subDirs(i)
的锁保护的。- shutdownHook:即关闭钩子,在进程结束时会递归删除localDirs下所有属于该Application的文件。
主要功能
DiskBlockManager管理了每个BlockId到磁盘上文件的映射关系。DiskBlockManager主要有如下几个功能:
- 创建本地目录;
- 获取BlockId对应的文件路径;
- 查询BlockId对应的文件是否存在;
- 创建临时Block文件;
创建本地目录
负责创建配置的本地节点上的指定列表磁盘目录,用来存储Block数据到指定文件中。当使用外部shuffle服务时,在JVM退出时目录不会被删除。
private def createLocalDirs(conf: SparkConf): Array[File] = {
// 获取配置的本地目录列表
Utils.getConfiguredLocalDirs(conf).flatMap { rootDir =>
try {
// 创建目录,目录名为rootDir + "blockmgr-" + UUID.randomUUID.toString
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
}
}
}
获取BlockId对应的文件路径
如果Block数据想要落盘,首先需要通过调用getFile方法来分配一个唯一的文件路径(寻找该blockId对应文件也是通过这种方式)。其处理步骤如下:
-
根据文件名计算哈希值;
-
根据哈希值与本地文件一级目录的总数求余数,记为dirId;
-
根据哈希值与本地文件一级目录的总数求商数,此商数与二级目录的数目再求余数,记为subDirId;
-
如果dirId/subDirId目录存在,则获取dirId/subDirId目录下的文件,否则新建dirId/subDirId目录。
def getFile(blockId: BlockId): File = getFile(blockId.name) // 通过将文件名hash到本地子目录的方式来查找文件 // 该方法应该与"org.apache.spark.network.shuffle.ExternalShuffleBlockResolver#getFile()"方法保持同步 def getFile(filename: String): File = { // 得到该文件名hash到的本地目录,以及该目录下的子目录 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 } } new File(subDir, filename) }
查询BlockId对应的文件是否存在
查询BlockId对应的文件是否存在,也是通过获取BlockId对应的文件路径是否存在来实现的。
/** Check if disk block manager has a block. */
def containsBlock(blockId: BlockId): Boolean = {
getFile(blockId.name).exists()
}
创建临时Block文件
当ShuffleMapTask运行结束需要把中间结果临时保存,此时就调用createTempShuffleBlock方法创建临时的Block文件,并返回TempShuffleBlockId(TempShuffleBlockId的生成规则:"temp_shuffle_"后加上UUID字符串)与其对应的文件。
/** Produces a unique block id and File suitable for storing shuffled intermediate results. */
def createTempShuffleBlock(): (TempShuffleBlockId, File) = {
var blockId = new TempShuffleBlockId(UUID.randomUUID())
while (getFile(blockId).exists()) {
blockId = new TempShuffleBlockId(UUID.randomUUID())
}
(blockId, getFile(blockId))
}
/** Id associated with temporary shuffle data managed as blocks. Not serializable. */
private[spark] case class TempShuffleBlockId(id: UUID) extends BlockId {
override def name: String = "temp_shuffle_" + id
}