基于源码hadoop-3.3.0
1 概述
众所周知,dn主要是用来存储hadoop集群中的具体的数据的。但实际上,Datanode还是需要保存一部分Datanode自身的元数据的, 这些元数据是通过Datanode磁盘存储上的一些文件和目录来保存的。
Datanode可以定义多个存储目录保存数据块,Datanode的多个存储目录存储的数据块并不相同,并且不同的存储目录可以是异构的, 这样的设计可以提高数据块IO的吞吐率[比如多块磁盘]。
1.1 实际存储
下面看一个实际中的存储:
我们简单了解一下:
- BP-607470660-10.200.50.133-1568802623014:BP表示blockpool,所以这个目录是一个块池目录,块池目录保存了一个块池在当前存储目录下存储的所有数据块, 在Federation部署方式中, Datanode的一个存储目录会包含多个以“BP”开头的块池目录。 BP后面会紧跟一个唯一的随机块池ID, 在这个示例中就是607470660。 接下来的IP地址10.200.50.133是当前块池对应的Namenode的IP地址。 最后一个部分是这个块池的创建时间。
- VERSION文件:在current中存在一个VERSION文件,截图未显示,内容如下
块池目录的VERSION文件同样包含了文件系统布局版本(layoutVersion) 、 HDFS集群ID(clusterld) 以及创建时间(cTime) 等集群信息。 除此之外, 块池目录的VERSION文件还包含了以下信息。
-
- storageType:存储类型,这里是DATA_NODE
- current/BP-607470660-10.200.50.133-1568802623014/current目录:
-
- finalized/rbw:finalized和rbw目录都是用于存储数据块的, 包括数据块文件以及对应的校验和文件。 rbw(replica being written, 正在写入副本) 目录保存了正在由HDFS客户端写入当前Datanode的数据块。 finalized目录包含了已经完成写入操作的数据块, 由于这样的数据块可能非常多, 所以finalized目录会以特定的目录结构存储这些数据块。每个数据块对应 2 个文件,blk 文件存放数据,另外一个以 meta 结尾的存放校验和等元数据
- VERSION:这里会记录namespaceID,cTime,blockpoolId,layoutVersion
- current/BP-607470660-10.200.50.133-1568802623014/scanner.cursor:DataNode 会定期的对每个 blk 文件做校验,这个文件是用来记录校验到哪个位置的
- in_use.lock:被dn线程持有的锁文件,用于防止多个Datanode线程启动并且并发修改这个存储目录
- lazyPersist: HDFS 2.X中引入了一个新的特性, 用于支持将临时数据写入内存, 然后通过懒持久化(lazyPersist) 方式写入磁盘。 如果用户开启了这个特性,lazyPersist目录就用于将内存中的临时数据懒持久化到磁盘。
1.2 功能划分
Datanode最重要的功能就是管理磁盘上存储的HDFS数据块(block)。
Datanode将这个管理功能切分为两个部分:
① 管理与组织磁盘存储目录(由dfs.data.dir指定) , 如current、previous、 detach、 tmp等, 这个功能由DataStorage类实现;
②管理与组织数据块及其元数据文件, 这个功能主要由FsDatasetImpl(对应每一个存储目录)相关类实现。
本文主要介绍第一部分:磁盘存储目录的管理。
2 磁盘存储目录管理
在dn中负责管理磁盘存储的主要继承结构如下:
因此我们分别了解一下这几个概念。
2.1 Storage
根据注释,此类用于存储storage file信息,本地的storage 信息存储在VERSION文件中,主要包括node的类型,存储布局版本,namespaceId,fs state create time。
本地存储可以驻留在多个目录中。每个目录都应包含与其他目录相同的VERSION 文件。在启动Hadoop服务器(名称节点和数据节点)期间读取它们的本地存储来自他们的信息。
服务器在运行时为每个存储目录持有一个锁,以便其他节点无法启动共享相同的存储。当服务器停止(正常或异常)时释放锁。
Storage是一个抽象类, 为Datanode、 Namenode提供抽象的存储服务。 Storage类管理着当前节点上( 可以是Datanode或者Namenode) 所有的存储目录, 每个存储目录都由一个StorageDirectory对象管理,StorageDirectory类定义了存储目录上的通用操作。 由于HDFS 2.X版本引入了Federation机制 , Datanode会为多个块池保存数据块, HDFS定义了BlockPoolSliceStorage类来管理Datanode上的一个块池, 这个块池分布在Datanode配置的所有存储目录中。Storage用一个线性表字段storageDirs存储它管理的所有StorageDirectory, 并通过Dirlterator迭代器进行遍历。
private final List<StorageDirectory> storageDirs = new CopyOnWriteArrayList<>();
2.1.1 Storage.StorageState
StorageState定义了存储空间可能出现的所有状态。 在升级、 回滚、 升级提交、 检查点等操作中, 节点(Datanode或者Namenode) 的存储空间可能出现各种异常, 例如误操作、 断电、 宕机等情况, 这个时候存储空间就可能处于某种中间状态, 引入中间状态, 有利于HDFS从错误中恢复过来。 存储状态的确定, 是在StorageDirectory.analyzeStorage()方法中进行的.
public enum StorageState {
NON_EXISTENT, // 存储不存在
NOT_FORMATTED, // 存储未格式化
COMPLETE_UPGRADE, // 完成升级
RECOVER_UPGRADE, // 恢复升级
COMPLETE_FINALIZE, // 完成升级提交
COMPLETE_ROLLBACK, // 完成回滚操作
RECOVER_ROLLBACK, // 恢复回滚
COMPLETE_CHECKPOINT, // 完成检查点操作
RECOVER_CHECKPOINT, // 恢复检查点操作
NORMAL; // 正常状态
}
2.1.2 Storage.StorageDirectory
我们知道Datanode和Namenode都可以定义多个存储目录来存储数据,StorageDirectory是Storage的内部类, 定义了管理存储目录的通用方法。
重点字段:
// 存储目录的根, 就是java.io.File文件。
final File root; // root directory
// 指示当前目录是否是共享的。
// 例如在HA部署中, 不同的Namenode之间共享存储目录,
// 或者在Federation部署中不同的块池之间共享存储目录。
final boolean isShared;
// 当前存储目录的类型。
final StorageDirType dirType; // storage dir type
// 独占锁, java.nio.FileLock类型,
// 用来支持Datanode或者Namenode线程独占存储目录的锁操作。
FileLock lock; // storage lock
// 权限信息
private final FsPermission permission;
// 存储目录的标识符。
private String storageUuid = null; // Storage directory identifier.
// 位置信息
private final StorageLocation location;
StorageDirectory的操作主要包含:
2.1.2.1 获取文件夹
主要是获取存储目录中的各个文件和目录的方法:
包括:
- current和current Version文件
- previous和previous Version文件
- previous tmp目录
- removed tmp目录
- finlized tmp目录
- lastCheckpoint tmp目录
- previous.checkpoint文件
/**
* Directory {@code current} contains latest files defining
* the file system meta-data.
*
* @return the directory path
*/
public File getCurrentDir() {
if (root == null) {
return null;
}
return new File(root, STORAGE_DIR_CURRENT);
}
/**
* File {@code VERSION} contains the following fields:
* <ol>
* <li>node type</li>
* <li>layout version</li>
* <li>namespaceID</li>
* <li>fs state creation time</li>
* <li>other fields specific for this node type</li>
* </ol>
* The version file is always written last during storage directory updates.
* The existence of the version file indicates that all other files have
* been successfully written in the storage directory, the storage is valid
* and does not need to be recovered.
*
* @return the version file path
*/
public File getVersionFile() {
if (root == null) {
return null;
}
return new File(new File(root, STO