在Hadoop中fsimage保存最新的检查点信息,edits保存自最新检查点后的命名空间的变化。在分析hdfs namenode–format的源代码时,已经明确了该过程根据配置文件的信息创建fsimage和edits文件,这篇文章具体分析一下创建fsimage和edits文件的源代码。在NameNode的format方法中,有如下的代码:

FSImage fsImage = new FSImage(conf, nameDirsToFormat, editDirsToFormat);

try {
  FSNamesystem fsn = new FSNamesystem(conf, fsImage);
  fsImage.getEditLog().initJournalsForWrite();

  if (!fsImage.confirmFormat(force, isInteractive)) {
    return true; // aborted
  }

  fsImage.format(fsn, clusterId);
} catch (IOException ioe) {
  LOG.warn("Encountered exception during format: ", ioe);
  fsImage.close();
  throw ioe;
}

  在这段代码中主要涉及到了三个类,分别为FSImage、FSNamesystem和FSEditLog,其中FSImage负责检查点,FSEditLog维护命名空间变动的日志,FSNamesystem为DataNode执行实际的簿记工作。创建fsImage对象的源代码为

/**

  • Construct the FSImage. Set the default checkpoint directories.

*

  • Setup storage and initialize the edit log.

*

  • @param conf Configuration
  • @param imageDirs Directories the image can be stored in.
  • @param editsDirs Directories the editlog can be stored in.
  • @throws IOException if directories are invalid.

*/
protected FSImage(Configuration conf,Collection imageDirs,

                List<URI> editsDirs)throws IOException {

this.conf = conf;
/ NNStorage负责管理NameNode使用的StorageDirectories/
storage = new NNStorage(conf, imageDirs, editsDirs);
/*根据dfs.namenode.name.dir.restore的值决定是否尝试重新存储失败的存储目录

  • 默认值为false

*/

if(conf.getBoolean(DFSConfigKeys.DFS_NAMENODE_NAME_DIR_RESTORE_KEY,
                   DFSConfigKeys.DFS_NAMENODE_NAME_DIR_RESTORE_DEFAULT)) {
  storage.setRestoreFailedStorage(true);
}

this.editLog = new FSEditLog (conf, storage, editsDirs);
/*NNStorageRetentionManager负责检查NameNode的存储目录,

*并在fsimage和edits文件上执行保留策略。

*/

archivalManager = new NNStorageRetentionManager(conf, storage, editLog);

}

 FSImage的构造方法中,创建了NNStorage、FSEditLog和NNStorageRetentionManager对象,NNStorage的构造方法源代码如下:

public NNStorage(Configuration conf, Collection imageDirs, Collection editsDirs) throws IOException {

super(NodeType.NAME_NODE);
storageDirs = new CopyOnWriteArrayList<StorageDirectory>();

// this may modify the editsDirs, so copy before passing in
setStorageDirectories(imageDirs, 
                      Lists.newArrayList(editsDirs),
                      FSNamesystem.getSharedEditsDirs(conf));

}

  NNStorage中的setStorageDirectories方法用于初始化存储fsimage和edits文件的目录。这里不分析该方法的全部源代码,主要分析初始化fsimage的部分,如下:

// Add all name dirs with appropriate NameNodeDirType

for (URI dirName : fsNameDirs) {
  checkSchemeConsistency(dirName);
  boolean isAlsoEdits = false;
  for (URI editsDirName : fsEditsDirs) {
    if (editsDirName.compareTo(dirName) == 0) {
      isAlsoEdits = true;
      fsEditsDirs.remove(editsDirName);
      break;
    }
  }
  NameNodeDirType dirType = (isAlsoEdits) ?
                      NameNodeDirType.IMAGE_AND_EDITS :
                      NameNodeDirType.IMAGE;
  // Add to the list of storage directories, only if the
  // URI is of type file://
  if(dirName.getScheme().compareTo("file") == 0) {
    this.addStorageDir(new StorageDirectory(new File(dirName.getPath()),
        dirType,
        sharedEditsDirs.contains(dirName))); // Don't lock the dir if it's shared.
  }
}

  在该段代码中fsNameDirs和fsEditsDirs分别为参数dfs.namenode.name.dir和dfs.namenode.edits.dir设置的目录,默认情况下二者指向相同的目录。如果二者的值相同那么fsimage的类型就为NameNodeDirType.IMAGE_AND_EDITS,否则为NameNodeDirType.IMAGE。需要注意的是,到目前为止虽然根据配置文件创建了表示相应目录的File对象,但还没有在本地文件系统上创建实际的目录。现在回到FSIamge的构造方法中,在创建了NNStorage对象后,接下来创建FSEditLog对象,该类的构造方法为:

/**

  • Constructor for FSEditLog. Underlying journals are constructed, but
  • no streams are opened until open() is called.
  • @param conf The namenode configuration
  • @param storage Storage object used by namenode
  • @param editsDirs List of journals to use

*/
FSEditLog(Configuration conf, NNStorage storage, List editsDirs) {

isSyncRunning = false;
this.conf = conf;

this.storage = storage;
//在format阶段,metrics的值为null

metrics = NameNode.getNameNodeMetrics();
lastPrintTime = now();
// If this list is empty, an error will be thrown on first use
// of the editlog, as no journals will exist
this.editsDirs = Lists.newArrayList(editsDirs);
this.sharedEditsDirs = FSNamesystem.getSharedEditsDirs(conf);

}

  在创建完了FSEditLog和NNStorageRetentionManager对象后,FSIamge的构造方法执行完毕,回到NameNode的format方法中。接下来就是要根据FSImage对象创建FSNamesystem,前面曾提到FSNamesystem为DataNode执行实际的簿记工作,实际执行的该类的构造方法如下,其中ignoreRetryCache的值为false:

推荐:Hadoop源码分析之DFSClient对象的创建

[再上一篇文章Hadoop源码分析之DistributedFileSystem中说道:DistributedFileSystem的重点在于其成员变量DFSClient dfs,它执行了文件系统的具体的操作。所以现在就来学习

/**

  • Create an FSNamesystem associated with the specified image.
  • Note that this does not load any data off of disk -- if you would
  • like that behavior, use {@link #loadFromDisk(Configuration)}

*

  • @param conf configuration
  • @param fsImage The FSImage to associate with
  • @param ignoreRetryCache Whether or not should ignore the retry cache setup
  • step. For Secondary NN this should be set to true.
  • @throws IOException on bad configuration

*/
FSNamesystem(Configuration conf, FSImage fsImage, boolean ignoreRetryCache)

  throws IOException

  由于该构造方法代码比较多,就不贴出来了。可以概括为在该构造方法中,实例化了诸如BlockManager、DatanodeStatistics、FSDirectory、CacheManager等对象,读取了配置文件中的一些信息(后面计划专门研究该类)。在创建完毕FSNamesystem对象后,执行的是FSEditLog的initJournalsForWrite()方法,该方法的代码如下:

private State state = State.UNINITIALIZED;
public synchronized void initJournalsForWrite() {

Preconditions.checkState(state == State.UNINITIALIZED ||
    state == State.CLOSED, "Unexpected state: %s", state);
initJournals(this.editsDirs);
state = State.BETWEEN_LOG_SEGMENTS;

}

  在该方法中先检查日志的状态,FSEditLog刚创建时的状态为UNINITIALIZED,执行完initJournals()方法后日志的状态为BETWEEN_LOG_SEGMENTS,意味日志还未打开。日志的初始化具体由initJournals()完成,该方法的代码如下:

private synchronized void initJournals(List dirs) {

int minimumRedundantJournals = conf.getInt(
    DFSConfigKeys.DFS_NAMENODE_EDITS_DIR_MINIMUM_KEY,
    DFSConfigKeys.DFS_NAMENODE_EDITS_DIR_MINIMUM_DEFAULT);

//管理日志集合的对象

journalSet = new JournalSet(minimumRedundantJournals);

for (URI u : dirs) {
  boolean required = FSNamesystem.getRequiredNamespaceEditsDirs(conf)
      .contains(u);
  if (u.getScheme().equals(NNStorage.LOCAL_URI_SCHEME)) {
    StorageDirectory sd = storage.getStorageDirectory(u);
    if (sd != null) {
      journalSet.add(new FileJournalManager(conf, sd, storage),
          required, sharedEditsDirs.contains(u));
    }
  } else {
    journalSet.add(createJournal(u), required,
        sharedEditsDirs.contains(u));
  }
}

if (journalSet.isEmpty()) {
  LOG.error("No edits directories configured!");
} 

}

  该方法根据传递的目录集合创建FileJournalManager对象,并添加到JournalSet对象中,其中一个FileJournalManager对象管理一个保存edits文件的目录。初始化edits文件后,接下来就是对fsimage进行格式化了,具体代码为:

void format(FSNamesystem fsn, String clusterId) throws IOException {

long fileCount = fsn.getTotalFiles();
// Expect 1 file, which is the root inode
Preconditions.checkState(fileCount == 1,
    "FSImage.format should be called with an uninitialized namesystem, has " +
    fileCount + " files");
NamespaceInfo ns = NNStorage.newNamespaceInfo();
LOG.info("Allocated new BlockPoolId: " + ns.getBlockPoolID());
ns.clusterID = clusterId;

storage.format(ns);
editLog.formatNonFileJournals(ns);
saveFSImageInAllDirs(fsn, 0);

}

  在该代码中实际执行创建fsimage文件的代码为最后一行的saveFSImageInAllDirs(fsn,0),该方法将实际的工作委托给下面的方法:

saveFSImageInAllDirs(source, NameNodeFile.IMAGE, txid, null);

  在该方法中会根据保存fsimage的目录的个数创建与之相等的线程,这些线程完成实际的创建fsimage的任务。最后会清理旧的edits和检查点文件。

  通过上面的分析可以发现,在执行hdfs namenode –format时仅创建了fsimage文件,并没有创建edits文件,但已经创建了相关对象。这一点也可以在执行完format后在本地文件系统中确认,目录中只有fsimage文件。