Hadoop-组件-HDFS-源码学习-元数据管理-持久化 EditLog-EditLogOutputStream
一、概述
目前 Namenode 可以在多种类型的异构存储上保存 editlog 文件,例如普通文件系统、共享 NFS、 Bookkeeper 以及 Quorum 集群等,所以 EditLogOutputStream 定义了多个子类来向不同存储系统上的 editlog 文件中写入数据。
EditLogFileOutputStream 抽象了本地文件系统上 editlog 文件的输出流,BookKeeperEditLogOutputStream 抽象了 BookKeeper 系统上 editlog 文件的输出流,QuorumOutputStream 抽象了 Quorum 集群上 editlog 文件的输出流。
同时由于 Namenode 可以同时向多个不同的存储上写入 editlog 文件,所以 EditLogOutputStream 还定义了子类 JournalSetOutputStream 执行聚合的写入操作。
二、实现
JournalSetOutputStream 类是 EditLogOutputStream 的子类,在 JournalSetOutputStream 对象上调用的所有 EditLogOutputStream接口方法都会被前转到 FSEditLog.journalSet字段中保存的 editlog 文件在所有存储位置上的输出流对象上
FSEditLog 的 editLogStream 字段是 JournalSetOutputStream 类型(在 startLogSegment() 方法中初始化),通过调用JournalSetOutputStream 对象提供的方法,FSEditLog 将 Namenode 多个存储位置上的 editlog 文件输出流对外封装成一个输出流,方便调用。
2.1. 初始化 EditLogOutputStream
来看看 editLogStream 的初始化方法 startLogSegment() ~~~
1 | synchronized void startLogSegment(final long segmentTxId, |
startLogSegment() 方法调用了 journalSet.startLogSegment()方法在所有 editlog 文件的存储路径上构造输出流
1 | editLogStream = journalSet.startLogSegment(segmentTxId, NameNodeLayoutVersion.CURRENT_LAYOUT_VERSION); |
journalSet 又是什么🤔️?
journalSet 字段是 JournalManager 对象的集合。JournalManager 类负责在特定存储目录上持久化 editlog 文件
2.1.1. 初始化 journalSet
initJournals() 方法会根据传入的 dirs 变量(保存的是 editlog 文件的存储位置,都是 URI) 初始化 journalSet 字段 。初始化之后,FSEditLog 就可以调用 journalSet 对象的方法向多个日志存储位置写 editlog 文件了。
edits 文件的最小数量
1
2int minimumRedundantJournals = conf.getInt(DFSConfigKeys.DFS_NAMENODE_EDITS_DIR_MINIMUM_KEY,
DFSConfigKeys.DFS_NAMENODE_EDITS_DIR_MINIMUM_DEFAULT);本地文件系统
1
2
3
4
5
6
7if (u.getScheme().equals(NNStorage.LOCAL_URI_SCHEME)) {
StorageDirectory sd = storage.getStorageDirectory(u);
if (sd != null) {
// 如果是本地文件系统,则创建 FileJournalManager 对象, 这个对象专门管理元数据写入 namenode
journalSet.add(new FileJournalManager(conf, sd, storage), required, sharedEditsDirs.contains(u));
}
}不是本地系统 (JournalNode)
1
2
3
4
5// 不是本地系统(JournalNode)
else {
// 如果不是本地系统,那么会 createJournal -->QuorumJournalManager,这个对象专门管理元数据写入 journalnode
journalSet.add(createJournal(u), required, sharedEditsDirs.contains(u));
}
journalSet.add() 将输出流保存在 FSEditLog 的字段 journalSet.journals 中。journalSet 的 journals 字段是一个 JournalAndStream 对象的集合
集合中的每一个 JournalAndStream 对象都封装了一个 JournalManager,以及这个 JournalManager 打开的 editlog 文件的输出流,那么 journals 字段就保存了 editlog 文件在所有存储路径上的输出流。
2.1.2. journalSet.startLogSegment()
journalSet 初始化结束之后就可以调用其 startLogSegment() 方法啦~
1 | public EditLogOutputStream startLogSegment(final long txId, final int layoutVersion) throws IOException { |
journalSet.startLogSegment() 方法会返回一个 JournalSetOutputStream 对象,对象会保存在 FSEditLog 的 editLogStream 字段中,FSEditLog 之后进行的所有写操作都是通过 editLogStream 引用的 JournalSetOutputStream 对象进行的。JournalSetOutputStream 类是 EditLogOutputStream 的子类,在 JoumalSetOutputStream 对象上调用的所有接口方法都会被前转到 jounalSet.jourals 字段中保存的 editlog 文件在所有存储路径上的输出流对象上。journalSet 就是通过这种方式,将多个存储位置上的输出流对外封装成了一个输出流,方便了调用,如图 JournalSetOutputStream 调用流程:
EditLogFileOutputStream
EditLogFileOutputStream 是向本地文件系统中保存的 editlog 文件写数据的输出流,向 EditLogFileOutputStream 写数据时,数据首先被写入到输出流的缓冲区中,当显式地调用 flush 操作后,数据才会从缓冲区同步到 editlog 文件中。
Quorumoutputstream
QuorumOutputStream 抽象了 Quorum 集群上 editlog 文件的输出流。
startLogSegment()方法最后会将 FSEditlog.curSegmentTxId 字段(FSEditlog 当前正在写入 txid) 设置为传入的segmentTxid, 同时将 editlog 的状态更改为 IN_SEGMENT 状态。
2.2. EditLogOutputStream#write()
FSEditLog.editLogStream 字段的 write() 方法将数据写入到 editlog 文件输出流的缓存中~
1 | editLogStream.write(op); |