Hadoop-组件-HDFS-源码学习-元数据管理-更新 EditLog
看源码啦~~~😊
一、概述
目前 Namenode 的实现是将命名空间信息记录在 fsimage(命名空间镜像)的二进制文件中,fsimage 将文件系统目录树中的每个文件或者目录的信息保存为一条记录,每条记录中包括了该文件(或目录)的名称、大小、用户、用户组、修改时间、创建时间等信息。Namenode 重启时,会读取这个 fsimage 文件来重构命名空间。
为了避免 Namenode 两次持久化之间数据丢失的问题,又设计了 Edits log 编辑日志文件,HDFS 将新 fsimage 文件和上一个 fsimage 文件中进行的 Namenode 操作记录在 editlog (编辑日志) 文件中,editlog 是一个日志文件,文件中记录的是 HDFS 所有更改操作(文件创建,删除或修改)的日志,文件系统客户端执行的更改操作首先会被记录到 edits 文件中。
对于修改元数据的动作,比如申请上传一个文件,需要在内存文件目录树中加入一个文件,都要写一个 edits log,写 edits log 的两个步骤
- 写入本地磁盘
- 通过网络传输给 JournalNodes 集群
通过一个创建目录的代码
把元数据信息记录到 EditLog
1 | fsd.getEditLog().logMkDir(cur, newNode); |
二、封装 FSEditLogOp
面向对象的思路
1 | MkdirOp op = MkdirOp.getInstance(cache.get()) |
也就是最后 EditLog 中保存形式
三、FSEditLogOp —> EditLog
Editlog 文件会随着 Namenode 的运行实时更新,所以 FSEditLog 类的实现依赖于底层的输入流和输出流,同时 FSEditLog 类还需要对外提供大量的 log*() 方法用于记录命名空间的修改操作。
1 | logEdit(op); |
logEdit() 方法通过调用 beginTransaction() 方法成功地获取一个 transactionId 之后,就会通过输出流向 editlog 文件写数据以记录当前的操作,但是写入的这些数据并没有直接保存在 editlog 文件中,而是暂存在输出流的缓冲区中。
logEdit() 方法将一个完整的操作 Op 写入输出流后,调用 logSync() 方法同步当前线程对 editlog 文件所做的修改。
3.1. 等待调度
如果当前操作被其他线程调度了,则等待 1s
时间
1 | waitIfAutoSyncScheduled(); |
3.2. 设置事务 Id
3.2.1. 获取事务 Id
1 | private long beginTransaction() { |
3.2.2. 设置 事务 Id
1 | op.setTransactionId(txid); |
3.3. EditLog —> 双缓存(bufCurrent)
1 | try { |
3.3.1. EditLogOutputStream 初始化
3.3.2. EditLogOutputStream.write(op)
editLogStream 初始化后调用其 write()方法在 editlog 文件中记录一个操作,数据先被写入到 editlog 文件输出流的缓存中,EditLogOutputStream 提供了一种双 buffer 模式来缓存(EditsDoubleBuffer) 操作数据,将缓冲区分为 2 份,1 份为当前缓冲区 bufCurrent,另外 1 份为预写入分区 bufReady,两个缓冲区空间大小一致。current 区负责当前的写操作数据(Editlog)存放,当达到缓冲触发条件时,bufCurrent和 bufReady 调换。然后由另外的程序执行 bufReady 区的 flush 操作。 bufCurrent 区变为空缓冲区重新用于数据写入。
3.4. 结束事务
1 | endTransaction(start); |
3.5. 双缓存(bufCurrent)—>磁盘(本地/JournalNode)
logSync() 交换内存,把数据持久化到磁盘
3.5.1. 获取当前线程 ID
所有的操作项同步地写入缓存时,每个操作会被赋子一个唯一的 transactionId。当一个线程要将它的操作同步到 editlog 文件中时,logSync() 方法会使用 ThreadLocal 变量 transactionId 获取该线程需要同步的 transactionId
1 | long mytxid = myTransactionId.get().txid; |
3.5.2. 比较当前线程 Id 和最大事务 Id
对比当前线程的 transactionId 和已经同步到 editlog 文件中的 transactionId。如果当前线程的 transactionId 大于 editlog 文件中的transactionId,则表明 editlog 文件中记录的数据不是最新的,同时如果当前没有别的线程执行同步操作,则开始同步操作将输出流缓存中的数据写入 editlog 文件中。
由于 editlog 输出流使用了双 buffer的结构,所以在进行 sync 操作的同时,并不影响 editlog 输出流的使用。
在 logSync()方法中使用 isSyncRunning 变量标识当前是否有线程正在进行同步操作,
1 | /** |
3.5.3. 双缓冲区交换
输出流要进行同步操作时,首先要调用 EditsDoubleBuffer.setReadyToFlush() 方法交换两个缓冲区,将正在写入的缓存改变为同步缓存,然后才可以进行同步操作。
1 | editLogStream.setReadyToFlush(); |
3.5.4. 将缓冲区数据刷到磁盘
setReadyToFlush() 调用之后,输出流就可以调用 flush() 方法将同步缓存中的数据写入到文件中
1 | logStream.flush(); |
元数据写到本地磁盘中,同时还将元数据写到 Journalnode
EditLogFileOutputStream
QuorumOutputStream
HDFS HA实现 依赖于一个保存 editlog 的共享存储。这个共享存储必须是高可用的,并且能够被集群中的所有 Namenode 同时访问。
cloudera 提供了 QuorumJournal 设计方案,QuorumJournal 方案: HDFS 集群中有2N+1
个 JournalNode 存储 editlog 文件,这些 editog 文件是保存在 JournalNode 的本地磁盘上的。每个 JournalNode 对 QJM 暴露 RPC 接口 QJoumalProtocol,允许 Namenode 读写 editlog 文件。当 Namenode 向共享存储写入 editlog 文件时,它会通过 QJM 向集群中的所有 JN 发送写 editlog 文件请求,当有一半以上的(≥N+1)JN 返回写操作成功时即认为该次写成功。引用站外地址,不保证站点的可用性和安全性Hadoop-组件-HDFS-源码学习-元数据管理-持久化 EditLog-QuorumoutputstreamJoker
3.5.3. 下次刷写
1 | // 同时唤醒线程,通知该事务已经完成,可以进行下一次刷写 |