共有五种格式的文件:
edits_0000000000000041912-0000000000000041913:该 LogSegment 记录了 transaction id 在 41912-41913 之间的事务日志。(最多保留 50 个)
edits_inprogress_0000000000000041914:正在使用的 编辑日志文件,从 transaction id = 41914 开始,之后的事务都往这个文件中进行记录,直到进行了 rollLog 操作:生成一个新的 edits_inprogress_start_transaction_id 文件,把旧的 edits_inprogress_start_transaction_id 改名成 edits_(start_transaction_id)_(end_transaction_id) 。
fsimage_0000000000000041843 和 fsimage_0000000000000041843.md5:transaction id = 41843 以前的事务,都合并到了这个 fsimage 文件中了,.md5 文件存储了它的校验信息(最多保留 2 个)
seen_txid:存储最新的 fsimage 文件的 最新的 transaction id
VERSION:版本信息
类比 ZooKeeper 存储模型中的 DataTree + DataNode,NameNode 内存树抽象为 FSDirectory + INode(两个实现类:INodeDirectory: 内部有一个集合 List< INodeDirectory> 或 INodeFile + 文件内部 List< Block>),每个 INode 内部有维护有一个 INode parent。
也即 FSDirectory 维护的名称空间
上图涉及到的核心抽象:
public class FSImage implements Closeable {
// 管理磁盘编辑日志
protected FSEditLog editLog = null;
// 管理存储目录
protected NNStorage storage;
// 元数据存储目录格式化
void format(...) throws IOException {}
// 加载磁盘编辑日志恢复到内存
long loadEdits(...) throws IOException {}
// 加载磁盘 fsimage 镜像文件恢复到内存
void loadFSImage(....) throws IOException {}
// 生成 fsimage 镜像文件
void saveFSImage(...) throws IOException {}
// 保存命名空间
void saveNamespace(...) throws IOException {}
// 接收到 SecondaryNameNode 发送的要进行 checkpoint 的请求之后执行的操作
NamenodeCommand startCheckpoint(...) throws IOException {
CheckpointSignature sig = rollEditLog(layoutVersion);
return new CheckpointCommand(sig, needToReturnImg);
}
}
public class FSEditLog implements LogsPurgeable {
// dfs.namenode.shared.edits.dir 目录 = qjournal://bigdata02:8485;bigdata03:8485;bigdata04:8485/hadoop330ha
private final List<URI> editsDirs;
// dfs.namenode.shared.edits.dir 目录 = qjournal://bigdata02:8485;bigdata03:8485;bigdata04:8485/hadoop330ha
private final List<URI> sharedEditsDirs;
private JournalSet journalSet = null;
EditLogOutputStream editLogStream = null;
// 事务控制
private long beginTransaction() {}
private void endTransaction(long start) {}
// 记录日志操作, 记录一条日志到 edit_inprogress 文件里面
void logEdit(final FSEditLogOp op) {}
synchronized void logEdit(final int length, final byte[] data) {}
// 开启一个新的编辑日志 LogSegment
void startLogSegment() throws IOException {}
}
HDFS DataNode 支持多磁盘存储,通过以下配置生效:
<property>
<name>dfs.datanode.data.dir</name>
<value>dir1,dir2,dir3</value>
</property>
数据目录下的目录结构如下:
注意:每个 blockpool 对应联邦集群中的一个 NameService
DataNode 的每个数据存储目录结构示例如下:
// 这个 current 就表示是某一个 配置的存储目录(dir1, dir2, dir3 的其中之一)
├── current
// 块池目录
├── BP-2052521754-192.168.123.102-1614493867466
├── BP-2052521754-192.168.123.102-1614493867477
│ ├── current
│ │ ├── dfsUsed
│ │ ├── finalized
│ │ │ └── subdir0
│ │ │ ├── subdir0
│ │ │ ├── subdir1
│ │ │ ├── subdir10
│ │ │ │ ├── blk_1073744394
│ │ │ │ ├── blk_1073744394_3617.meta
│ │ │ │ ├── blk_1073744618
│ │ │ │ └── blk_1073744618_3841.meta
│ │ │ ├── subdir11
│ │ │ │ ├── blk_1073744640
│ │ │ │ ├── blk_1073744640_3863.meta
│ │ │ │ ├── blk_1073744892
│ │ │ │ └── blk_1073744892_4115.meta
│ │ │ ├── subdir12
│ │ │ │ ├── blk_1073744948
│ │ │ │ ├── blk_1073744948_4171.meta
│ │ │ │ ├── blk_1073745141
│ │ │ │ └── blk_1073745141_4375.meta
│ │ │ ├── subdir2
│ │ │ ├── subdir3
│ │ │ ├── subdir4
│ │ │ ├── subdir5
│ │ │ ├── subdir6
│ │ │ ├── subdir7
│ │ │ ├── subdir8
│ │ │ │ ├── blk_1073744026
│ │ │ │ ├── blk_1073744026_3243.meta
│ │ │ │ ├── blk_1073744038
│ │ │ │ └── blk_1073744038_3261.meta
│ │ │ └── subdir9
│ │ │ ├── blk_1073744252
│ │ │ ├── blk_1073744252_3475.meta
│ │ │ ├── blk_1073744377
│ │ │ └── blk_1073744377_3600.meta
│ │ ├── rbw
│ │ └── VERSION
│ ├── scanner.cursor
│ └── tmp
└── VERSION
├── in_use.lock
上述目录结构的一些相关解释:
BP-2052521754-192.168.123.102-1614493867477:在 HDFS Fedaration 集群中,有多个NameSpace,对应多组 NameNode,每一组 NameNode 都有一个独立的 BlockPool 块池,一个 BlockPool 块池目录保存了 该 BlockPool 在当前存储目录中的所有数据块。
VERSION:版本文件,该文件是一个标准的 Properties 文件,可自行查看
finalized:数据块存储目录,保存的是写入成功的数据块,包含数据块文件和 .meta 校验文件
rbw:数据块存储目录,保存的是正在写入的数据块,包含数据块文件和 .meta 校验文件
tmp:数据块存储目录
scanner.cursor:DataNode 对数据块进行校验的一个遍历游标,用来记录此时遍历到的数据块的位置
in_use.lock:DataNode 线程持有的目录锁,防止多线程对该文件夹进行修改操作
DataStorage 管理与组织磁盘存储目录。
public abstract class Storage extends StorageInfo {
// 一个 DataNode 可以配置多个存储目录
private final List<StorageDirectory> storageDirs = new CopyOnWriteArrayList<>();
}
public class DataStorage extends Storage {
public final static String BLOCK_SUBDIR_PREFIX = "subdir";
final static String STORAGE_DIR_DETACHED = "detach";
public final static String STORAGE_DIR_RBW = "rbw";
public final static String STORAGE_DIR_FINALIZED = "finalized";
public final static String STORAGE_DIR_LAZY_PERSIST = "lazypersist";
public final static String STORAGE_DIR_TMP = "tmp";
// BlockPoolSliceStorage 负责管理块池目录
private final Map<String, BlockPoolSliceStorage> bpStorageMap = Collections.synchronizedMap(
new HashMap<String, BlockPoolSliceStorage>());
}
管理和组织 DataNode 上的数据块及其元数据。
// FsDatasetImpl 负责为 DataNode 提供磁盘服务
class FsDatasetImpl implements FsDatasetSpi<FsVolumeImpl> {
// DataNode 上的所有数据块的一个管理载体
final ReplicaMap volumeMap;
// 存储目录管理组件
private final FsVolumeList volumes;
}
// FsVolumeList 就是对多个存储目录 FsVolumeImpl 的一个封装抽象
class FsVolumeList {
private final CopyOnWriteArrayList<FsVolumeImpl> volumes = new CopyOnWriteArrayList<>();
}
// 一个 FsVolumeImpl 负责管理一个存储目录下的所有数据块,一个存储目录下又有多个块池,所以映射到多个 BlockPoolSlice 来进行管理
public class FsVolumeImpl implements FsVolumeSpi {
// 记录存储位置信息
private final StorageLocation storageLocation;
// 对应的数据块管理组件
private final FsDatasetImpl dataset;
// 一个 FsVolumeImpl 映射到多个块池,BlockPoolSlice 负责管理一个块池
private final Map<String, BlockPoolSlice> bpSlices = new ConcurrentHashMap<String, BlockPoolSlice>();
}
class ReplicaMap {
// 一个 BlockPool 一个 TreeSet
private final Map<String, FoldedTreeSet<ReplicaInfo>> map = new HashMap<>();
}