继续为分析org.apache.hadoop.hdfs.server.namenode.FSNamesystem类做准备,这里分析与FSEditLog相关的几个类,当然,FSEditLog类才是核心的。
- FSEditLog.EditLogFileOutputStream内部静态类
该类是定义在org.apache.hadoop.hdfs.server.namenode.FSEditLog类内部的静态类,表示将EditLog日志通过打开一个该输出流实例写入到本地磁盘。该输出流类的继承层次结构如下所示:
首先,看EditLogOutputStream这个抽象类的定义。
EditLogOutputStream类是一个用来支持将EditLog日志文件中内容持久化(写入)到存储目录中的通用抽象类。该抽象类定义了两个统计变量,如下所示:
该抽象类中实现的方法都是与更新统计变量相关的,如下所示:
/**
* 获取执行flushAndSync()同步方法花费的总时间
*/
long getTotalSyncTime() {
return totalTimeSync;
}
/**
* 获取调用flushAndSync()方法执行同步的次数
*/
long getNumSync() {
return numSync;
}
该抽象类中定义了一些抽象方法,这些方法需要在该类的子类中给出具体实现:
/**
* 该方法继承自OutputStream,表示向该输出流对象中写入字节
*/
abstract public void write(int b) throws IOException;
/**
* 将EditLog日志记录写入到该输出流对象中
* (日志记录通过操作名称和一个Writable参数的数组来表征)
*/
abstract void write(byte op, Writable ... writables) throws IOException;
/**
* 创建并初始化一个新的EditLog日志存储对象
*/
abstract void create() throws IOException;
/** {@inheritDoc} */
abstract public void close() throws IOException;
/**
* 所有已经被写入到该输出流对象中的数据,将准备对其执行刷新(flush)操作。
* (在执行刷新的过程中,新的数据仍然能够被写入到该输出流对象中)
*/
abstract void setReadyToFlush() throws IOException;
/**
* Flush并且sync所有准备好的数据(调用了setReadyToFlush()方法表示准备),持久化到相应的(持久)存储中
*/
abstract protected void flushAndSync() throws IOException;
/**
* 获取当前EditLog日志文件的大小
* 得到EditLog日志文件的长度是为了检查日志文件是否大到需要启动一个检查点进程来处理
*/
abstract long length() throws IOException;
通过对EditLogOutputStream抽象类的分析,我们获知:一个EditLog日志文件也是通过流式进行写的,而且定时对日志文件进行同步,写入持久存储中,其中支持写入字节和Writable类型数据;在同步的过程中,需要记录同步统计数据;当EditLog日志文件达到一定大小的时候,需要启动检查点进程来进行处理。
然后,我们看FSEditLog.EditLogFileOutputStream内部静态类的具体实现。
该类继承自EditLogOutputStream抽象类,实现了该抽象类中定义的抽象方法,能够在一个本地文件(local file)中存储EditLog日志文件。
该类定义了如下属性:
下面是FSEditLog.EditLogFileOutputStream类的构造方法:
通过上面的构造方法,可见通过构造方法已经准备好了写EditLog日志文件的条件,定位到EditLog对应于本地存储文件流的当前写入位置,也就是说对日志文件的操作时追加写操作。
下面看该类中实现的方法:
@Override
public void write(int b) throws IOException {
bufCurrent.write(b); // 将字节写入当前数据缓冲区bufCurrent
}
@Override
void write(byte op, Writable ... writables) throws IOException {
write(op); // 调用重载的write方法,写入操作名称(其实是一个操作代码)
for(Writable w : writables) { // 迭代Writable对象数组
w.write(bufCurrent); // 分别写入到前数据缓冲区bufCurrent
}
}
/**
* 创建一个空的EditLog日志文件
*/
@Override
void create() throws IOException {
fc.truncate(0); // 设置当前文件的大小(如果文件大小大于0,删除文件中的全部字节数据)
fc.position(0); // 设置当前写位置(初始化)
bufCurrent.writeInt(FSConstants.LAYOUT_VERSION); // 在EditLog日志文件最前面写入版本号
setReadyToFlush(); // 设置准备刷新状态
flush(); // 执行刷新操作(将版本号写入到输出流中)
}
@Override
public void close() throws IOException {
// 在所有追加写事务执行flush与sync操作完成以后,该方法被调用
int bufSize = bufCurrent.size();
if (bufSize != 0) {
throw new IOException("FSEditStream has " + bufSize + " bytes still to be flushed and cannot " + "be closed.");
}
bufCurrent.close(); // 关闭当前数据缓冲区
bufReady.close(); // 关闭预执行flush准备数据缓冲区
fc.truncate(fc.position()); // 从事务日志中删除最后面的OP_INVALID标记(因为在调用setReadyToFlush方法结束时,写入了OP_INVALID标记)
fp.close(); // 关闭EditLog日志文件对应的本地存储文件输出流对象
bufCurrent = bufReady = null; // 释放
}
/**
* 所有已经被写入到该输出流对象中的数据,将准备对其执行刷新(flush)操作。
* (在执行刷新操作的过程中,新的数据仍然能够被写入到该输出流对象中)
*/
@Override
void setReadyToFlush() throws IOException {
assert bufReady.size() == 0 : "previous data is not flushed yet";
write(OP_INVALID); // 在文件末尾写入一个标记OP_INVALID
DataOutputBuffer tmp = bufReady; // 切换缓冲区:将当前的bufCurrent切换为准备好要执行flush操作的bufReady
bufReady = bufCurrent;
bufCurrent = tmp;
}
/**
* 将bufCurrent中数据到持久存储中
* currentBuffer is not flushed as it accumulates new log records
* while readyBuffer will be flushed and synced.
*/
@Override
protected void flushAndSync() throws IOException {
preallocate(); // 如果必要的话,为文件预分配内存
bufReady.writeTo(fp); // 将bufReady中数据写入到文件
bufReady.reset(); // 重启bufReady数据缓冲区
fc.force(false); // 因为调用preallocate()方法对文件进行了预分配,也就没必要更新该文件元数据信息
fc.position(fc.position()-1); // 设置当前写入位置:回退排除文件末尾的标记OP_INVALID
}
/**
* 获取当前EditLog日志文件的大小(包括bufReady与bufCurrent)
*/
@Override
long length() throws IOException {
return fc.size() + bufReady.size() + bufCurrent.size(); // 返回文件大小 = 文件实际大小 + bufReady大小 + bufCurrent大小
}
// 为日志文件预分配一个大数据块
private void preallocate() throws IOException {
long position = fc.position(); // 获取文件当前位置
if (position + 4096 >= fc.size()) {
FSNamesystem.LOG.debug("Preallocating Edit log, current size " + fc.size());
long newsize = position + 1024*1024; // 1MB
fill.position(0); // 预分配数据缓冲区fill写入位置为0
int written = fc.write(fill, newsize); // 为日志文件增加分配newsize个字节
FSNamesystem.LOG.debug("Edit log size is now " + fc.size() + " written " + written + " bytes " + " at offset " + newsize);
}
}
/**
* 获取与该输出流相关联的文件
*/
File getFile() {
return file;
}
通过对上面方法的阅读分析,可以了解到,EditLog日志文件是与本地磁盘上的一个文件相对应的,实际也是写入到这个本地文件中的。在执行流式追加写的过程中,设置了两个重要的数据缓冲区,bufReady 与bufCurrent,通过切换这两个数据缓冲区来将不断追加写入到EditLogFileOutputStream流对象中的数据,同步到本地磁盘文件中。而且,当执行flushAndSync方法进行同步时候,如果必要会对EditLog日志文件对应的本地文件进行预分配,保证可持续做好写事务操作的记录。
- FSEditLog.EditLogFileInputStream内部静态类
该类org.apache.hadoop.hdfs.server.namenode.FSEditLog.EditLogFileInputStream是对EditLog日志文件进行读取的输入流实现类。该输入流类与EditLogFileOutputStream输出流类的组织关系类似,它的继承层次关系如下所示:
首先,看抽象流类EditLogInputStream。该类是一个用来支持读取本地存储目录中对应的文件,从而得到EditLog日志文件的通用抽象类。该抽象类比较简单,定义获取继承了一组抽象方法,如下所示:
/**
* 估算大概存在多少字节可读的数据
*/
public abstract int available() throws IOException;
/**
* 从该输入流中读取下一个字节的数据
*/
public abstract int read() throws IOException;
/**
* 从该输入流off位置读取len个字节到字节数组b中
*/
public abstract int read(byte[] b, int off, int len) throws IOException;
/**
* 关闭该输入流,释放相关资源
*/
public abstract void close() throws IOException;
/**
* 获取当前EditLog日志文件的大小
*/
abstract long length() throws IOException;
没什么可说的,继续看FSEditLog.EditLogFileInputStream内部静态类的具体实现吧。
FSEditLog.EditLogFileInputStream内部静态类也比较简单,就是提供了对EditLog日志文件的读取操作,也是基于流的,该类源代码如下所示:
EditLogFileInputStream(File name) throws IOException { // 构造方法
file = name;
fStream = new FileInputStream(name); // 构造file的输入流实例
}
@Override
String getName() { // 获取file的路径
return file.getPath();
}
@Override
public int available() throws IOException {
return fStream.available(); // 获取从该输入流可以读取到的字节数
}
@Override
public int read() throws IOException {
return fStream.read(); // 从该输入流中读取一个字节的数据
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return fStream.read(b, off, len); // 将该输入流中从off位置开始读取len个字节到字节数组b中
}
@Override
public void close() throws IOException {
fStream.close(); // 关闭该输入流
}
@Override
long length() throws IOException {
return file.length(); // 返回文件大小 = 文件实际大小 + bufReady大小 + bufCurrent大小
}
}
- FSEditLog类
该类维护一个记录对文件系统命名空间进行修改操作的日志文件。
我们已经对FSEditLog.EditLogFileOutputStream类与FSEditLog.EditLogFileInputStream类非常熟悉了,它们是与EditLog日志文件的读写密切相关的。
在FSEditLog类内部还有一个实现了Writable接口的可序列化内实体类FSEditLog.BlockTwo,它能够从以旧格式存储的块中读写数据,如下所示:
static { // 注册
WritableFactories.setFactory
(BlockTwo.class,
new WritableFactory() {
public Writable newInstance() { return new BlockTwo(); }
});
}
BlockTwo() {
blkid = 0;
len = 0;
}
public void write(DataOutput out) throws IOException {
out.writeLong(blkid);
out.writeLong(len);
}
public void readFields(DataInput in) throws IOException {
this.blkid = in.readLong();
this.len = in.readLong();
}
}
通过一个BlockTwo对象,能够读写块的ID和块的长度。
另一个是事务ID的封装内部实体类FSEditLog.TransactionId,表示EditLog日志文件记录的修改操作这样的事务ID,在FSEditLog类中主要是用来作为当前执行事务的线程对应的线程局部变量的拷贝:ThreadLocal<TransactionId>。该类如下所示:
TransactionId(long value) {
this.txid = value;
}
}
FSEditLog.TransactionId类只封装了一个事务ID,再没有其它内容了。
我们再看FSEditLog类。
首先看,对文件系统命名空间进行不同操作的操作代码,通过向EditLog日志文件中写入对应的操作代码来表示实际的操作,这些操作代码包含:
再看该类中定义的其它属性,如下所示:
private long txid = 0; // 单调递增事务ID计数器,用来为事务分配ID的
private long synctxid = 0; // 最后执行sync同步操作的事务ID
private long lastPrintTime; // 最后一次将统计数据输出到日志文件的时间
private boolean isSyncRunning; // 是否当前正在执行sync同步操作
private long numTransactions; // 事务数量统计变量
private long numTransactionsBatchedInSync; // 批量sync事务的数量的统计变量
private long totalTimeTransactions; // 执行全部事务的时间统计变量
private NameNodeMetrics metrics; // Namenode统计数据的统计变量
// 存储线程的当前事务
private static final ThreadLocal<TransactionId> myTransactionId = new ThreadLocal<TransactionId>() {
protected synchronized TransactionId initialValue() {
return new TransactionId(Long.MAX_VALUE);
}
};
该类的构造方法如下所示:
关于一个FSEditLog类的实例能够做哪些事情,我们从该类中挑选几个比较关键的方法来详细解释说明。
1、创建一个EditLog日志文件
如下所示:
另外,还有一个用来表示当前最新的edit文件edit.new,当该文件丢失的时候,需要进行创建,该操作对应于方法createNewIfMissing,如下所示:
2、打开 一个EditLog日志文件
方法open实现了打开日志文件的功能。实际上,当创建一个FSEditLog类的实例以后(通过构造方法构造得到),调用open方法打开一个该文件的输出流,准备后继向流中持续追加日志记录。open方法如下所示:
调用open方法,其实是从Namenode来加载EditLog日志文件所对应的全部存储目录,并打开每一个与EditLog日志文件相关的本地文件的文件输出流,为了便于FSEditLog类实例管理这些输出流,使用一个ArrayList来存放于内存中。
3、处理输出流IO异常
通过前面分析,一个FSEditLog类的实例维护一个EditLogOutputStream输出流对象列表,那么当某个输出流对象发生IO异常的时候,需要对其作出处理,而不能影响其它事务的执行。 该类中给出了三个处理IO异常的方法,如下所示:
/**
* 如果发生与EditLog日志相关的IO异常,会从editStreams列表中删除掉发生IO异常的输出流对象
* 第二个:根据存储目录名称来处理editStreams列表中对应的输出流对象
*/
synchronized void processIOError(StorageDirectory sd);
/**
* 如果发生与EditLog日志相关的IO异常,会从editStreams列表中删除掉发生IO异常的输出流对象
* 第三个:对列表editStreams中的输出流对象进行批量检查并对发生IO异常的输出流对象处理
*/
private void processIOError(ArrayList<EditLogOutputStream> errorStreams);
在FSEditLog类中处理IO异常的时候,还需要将处理的情况报告到FSImage中,因为FSImage也维护了一个指定为要删除的存储目录列表,保证数据状态的同步与一致。
4、加载EditLog日志文件
实现的方法为loadFSEdits方法,因为加载一个EditLog日志文件的时候,需要将对应EditLog日志文件记录内容应用到一个滞留于内存中的结构上,保证内存中对应结构与日志同步。因为该方法比较重要,通过它能够看到如何在日志文件与其对应的内存映像之间进行切换,尽管该方法代码比较多。我还是贴出来分析,能够更好地看到这个过程,加深理解。必要的时候我会对代码行修改,减少显示行数。
loadFSEdits方法实现如下所示:
DataInputStream in = new DataInputStream(new BufferedInputStream(edits)); // EditLog日志文件对应的输入流
try {
in.mark(4); // 设置EditLog日志文件输入流当前位置,从而读取日志文件的版本(很可能发生版本号信息丢失的情况)
// 如果EditLog日志文件大于2G,调用available方法将会返回一个负数,为了避免不得不调用available方法的情况,设置一个Boolean变量,并指定其值为true
boolean available = true;
try {
logVersion = in.readByte(); // 从输入流中读取版本号信息
} catch (EOFException e) {
available = false; // 发生异常则置于available为false,表示当前EditLog日志文件不可用(可能日志文件存在问题)
}
if (available) { // 日志文件可用
in.reset(); // 重置
logVersion = in.readInt(); // 读取版本号
if (logVersion < FSConstants.LAYOUT_VERSION) // 判断读取到的版本号是否合法
throw new IOException("Unexpected version of the file system log file: " + logVersion + ". Current version = " + FSConstants.LAYOUT_VERSION + ".");
}
assert logVersion <= Storage.LAST_UPGRADABLE_LAYOUT_VERSION : "Unsupported version " + logVersion;
while (true) {
long timestamp = 0, mtime = 0, atime = 0, blockSize = 0;
byte opcode = -1;
try {
opcode = in.readByte(); // 读取一个字节
if (opcode == OP_INVALID) { // 因为我们已经知道,在开始写日志的时候,写完版本号以后,可能写入一个字节的OP_INVALID(也就是-1);并且在写日志过程中,会将这个标记删除掉的,如果没有及时删除掉,很可能在该日志上根本没有事务记录,也可能发生了IO异常
FSNamesystem.LOG.info("Invalid opcode, reached end of edit log " + "Number of transactions found " + numEdits);
break; // 没有事务记录,直接退出,不再读取EditLog日志文件了
}
} catch (EOFException e) {
break; // 没有事务记录
}
numEdits++; // 每读取到EditLog日志文件中一个记录的事务,就增加一个计数
switch (opcode) { // 根据读取到的操作代码,进行相应的处理
case OP_ADD:
case OP_CLOSE: { // OP_ADD(添加)或OP_CLOSE(关闭)文件操作
int length = in.readInt();
if (-7 == logVersion && length != 3|| -17 < logVersion && logVersion < -7 && length != 4 || logVersion <= -17 && length != 5) { // 判断读取到的版本号是否合法
throw new IOException("Incorrect data format." + " logVersion is " + logVersion + " but writables.length is " + length + ". ");
}
path = FSImage.readString(in); // 读取文件路径
short replication = adjustReplication(readShort(in)); // 读取副本数(必要的话需要修改该值)
mtime = readLong(in); // 读取文件修改时间
if (logVersion <= -17) {
atime = readLong(in); // 如果版本号小于-17,读取文件访问时间
}
if (logVersion < -7) {
blockSize = readLong(in); // 读取块大小
}
Block blocks[] = null;
if (logVersion <= -14) {
blocks = readBlocks(in); // 读取块列表
} else {
BlockTwo oldblk = new BlockTwo();
int num = in.readInt();
blocks = new Block[num];
for (int i = 0; i < num; i++) {
oldblk.readFields(in);
blocks[i] = new Block(oldblk.blkid, oldblk.len, Block.GRANDFATHER_GENERATION_STAMP);
}
}
// 旧版本HDFS没有在文件中存储块大小,如果某个文件块多于1个,使用第一个作为块大小,否则使用默认值
if (-8 <= logVersion && blockSize == 0) {
if (blocks.length > 1) {
blockSize = blocks[0].getNumBytes();
} else {
long first = ((blocks.length == 1)? blocks[0].getNumBytes(): 0);
blockSize = Math.max(fsNamesys.getDefaultBlockSize(), first);
}
}
PermissionStatus permissions = fsNamesys.getUpgradePermission();
if (logVersion <= -11) {
permissions = PermissionStatus.read(in); // 读取权限
}
// 读取文件中最后一个块的clientname, clientMachine and block locations
if (opcode == OP_ADD && logVersion <= -12) {
clientName = FSImage.readString(in);
clientMachine = FSImage.readString(in);
if (-13 <= logVersion) {
readDatanodeDescriptorArray(in);
}
} else {
clientName = "";
clientMachine = "";
}
// The open lease transaction re-creates a file if necessary.
if (FSNamesystem.LOG.isDebugEnabled()) {
FSNamesystem.LOG.debug(opcode + ": " + path + " numblocks : " + blocks.length + " clientHolder " + clientName + " clientMachine " + clientMachine);
}
fsDir.unprotectedDelete(path, mtime); // 从namespace中删除该文件,并更新目录配额信息
// 将path添加到文件树fsDir中
INodeFile node = (INodeFile)fsDir.unprotectedAddFile(path, permissions, blocks, replication, mtime, atime, blockSize);
if (opcode == OP_ADD) { // 如果是添加操作码
numOpAdd++; // 统计
INodeFileUnderConstruction cons = new INodeFileUnderConstruction(
node.getLocalNameBytes(), node.getReplication(), node.getModificationTime(),
node.getPreferredBlockSize(), node.getBlocks(), node.getPermissionStatus(),
clientName, clientMachine, null);
fsDir.replaceNode(path, node, cons); // 使用cons替换node
fsNamesys.leaseManager.addLease(cons.clientName, path); // 将cons加载到内存中,加入到租约管理器中
}
break;
}
case OP_SET_REPLICATION: { // 设置副本数操作
numOpSetRepl++; // 统计
path = FSImage.readString(in); // 读取文件
short replication = adjustReplication(readShort(in)); // 读取并调整副本数
fsDir.unprotectedSetReplication(path, replication, null); // 更新目录fsDir
break;
}
case OP_RENAME: { // 重命名操作
numOpRename++; // 统计
int length = in.readInt(); // 读取长度
if (length != 3) {
throw new IOException("Incorrect data format. " + "Mkdir operation.");
}
String s = FSImage.readString(in); // 读取源文件名称
String d = FSImage.readString(in); // 读取重命名后文件名称
timestamp = readLong(in); // 读取重命名文件时间戳
FileStatus dinfo = fsDir.getFileInfo(d); // 获取文件d的描述信息
fsDir.unprotectedRenameTo(s, d, timestamp); // 更新目录fsDir
fsNamesys.changeLease(s, d, dinfo); // 修改租约信息
break;
}
case OP_DELETE: { // 删除操作
numOpDelete++; // 统计
int length = in.readInt(); // 删除文件名称长度
if (length != 2) {
throw new IOException("Incorrect data format. " + "delete operation.");
}
path = FSImage.readString(in); // 读取长度
timestamp = readLong(in); // 读取删除文件时间戳
fsDir.unprotectedDelete(path, timestamp); // 执行删除
break;
}
case OP_MKDIR: { // 创建目录操作
numOpMkDir++; // 统计
PermissionStatus permissions = fsNamesys.getUpgradePermission(); // 获取文件系统中路径的默认权限
int length = in.readInt();
if (-17 < logVersion && length != 2 || <= -17 && length != 3) {
throw new IOException("Incorrect data format. " + "Mkdir operation.");
}
path = FSImage.readString(in); // 读取目录名称
timestamp = readLong(in); // 读取创建目录时间戳
if (logVersion <= -17) {
atime = readLong(in);
}
if (logVersion <= -11) {
permissions = PermissionStatus.read(in);
}
fsDir.unprotectedMkdir(path, permissions, timestamp); // 在fsDir目录中创建目录path
break;
}
case OP_SET_GENSTAMP: { // 设置时间戳操作
numOpSetGenStamp++; // 统计
long lw = in.readLong(); // 读取设置的时间戳
fsDir.namesystem.setGenerationStamp(lw); // 在fsDir中设置时间戳
break;
}
case OP_DATANODE_ADD: { // 丢弃的操作码
numOpOther++;
FSImage.DatanodeImage nodeimage = new FSImage.DatanodeImage();
nodeimage.readFields(in);
break;
}
case OP_DATANODE_REMOVE: { // 丢弃的操作码
numOpOther++;
DatanodeID nodeID = new DatanodeID();
nodeID.readFields(in);
break;
}
case OP_SET_PERMISSIONS: { // 设置权限
numOpSetPerm++; // 统计
if (logVersion > -11)
throw new IOException("Unexpected opcode " + opcode + " for version " + logVersion);
fsDir.unprotectedSetPermission(FSImage.readString(in), FsPermission.read(in)); // 在fsDir中同步设置
break;
}
case OP_SET_OWNER: { // 设置属主操作
numOpSetOwner++;
if (logVersion > -11)
throw new IOException("Unexpected opcode " + opcode + " for version " + logVersion);
fsDir.unprotectedSetOwner(FSImage.readString(in), FSImage.readString_EmptyAsNull(in), FSImage.readString_EmptyAsNull(in)); // 在fsDir中同步设置属主
break;
}
case OP_SET_NS_QUOTA: { // 设置namespace配额操作
if (logVersion > -16) {
throw new IOException("Unexpected opcode " + opcode + " for version " + logVersion);
}
fsDir.unprotectedSetQuota(FSImage.readString(in), readLongWritable(in), FSConstants.QUOTA_DONT_SET); // 在fsDir中同步设置namespace配额
break;
}
case OP_CLEAR_NS_QUOTA: { // 清除namespace配额操作
if (logVersion > -16) {
throw new IOException("Unexpected opcode " + opcode + " for version " + logVersion);
}
fsDir.unprotectedSetQuota(FSImage.readString(in), FSConstants.QUOTA_RESET, FSConstants.QUOTA_DONT_SET); // 在fsDir中同步清除namespace配额
break;
}
case OP_SET_QUOTA: // 设置名称和磁盘配额
fsDir.unprotectedSetQuota(FSImage.readString(in), readLongWritable(in), readLongWritable(in)); // 在fsDir中同步设置磁盘配额
break;
case OP_TIMES: { // 设置文件的mod & access时间操作
numOpTimes++;
int length = in.readInt();
if (length != 3) {
throw new IOException("Incorrect data format. " + "times operation.");
}
path = FSImage.readString(in);
mtime = readLong(in);
atime = readLong(in);
fsDir.unprotectedSetTimes(path, mtime, atime, true); // 在fsDir上同步设置
break;
}
default: {
throw new IOException("Never seen opcode " + opcode);
}
}
}
} finally {
in.close();
}
FSImage.LOG.info("Edits file " + edits.getName() + " of size " + edits.length() + " edits # " + numEdits + " loaded in " + (FSNamesystem.now()-startTime)/1000 + " seconds.");
if (FSImage.LOG.isDebugEnabled()) {
FSImage.LOG.debug("numOpAdd = " + numOpAdd + " numOpClose = " + numOpClose
+ " numOpDelete = " + numOpDelete + " numOpRename = " + numOpRename
+ " numOpSetRepl = " + numOpSetRepl + " numOpMkDir = " + numOpMkDir
+ " numOpSetPerm = " + numOpSetPerm
+ " numOpSetOwner = " + numOpSetOwner
+ " numOpSetGenStamp = " + numOpSetGenStamp
+ " numOpTimes = " + numOpTimes
+ " numOpOther = " + numOpOther);
}
if (logVersion != FSConstants.LAYOUT_VERSION) // 如果版本号不等于LAYOUT_VERSION=-8
numEdits++; // 也进行统计
return numEdits;
}
通过该方法的实现,实际上在从多个EditLog 中读取日志信息的时候,主要是将日志文件中对应的事务,通过FSDirectory fsDir = fsNamesys.dir及时更新到文件系统的目录上,保持数据状态同步一致。
5、其它方法
还有几个方法,分别表示对EditLog日志文件的操作,比如:
关闭日志文件方法rollEditLog,同时创建一个edits.new文件;
删除旧日志文件purgeEditLog方法,同时将edits.new文件重命名为edits;
同步日志文件方法logSync,只对当前线程对日志文件作出的全部修改,同步到日志文件上。