namenode加载元数据
在文档(9)中分析namenode加载元数据的函数调用关系。 最终其会调用FSImage类的loadFSImage方法来加载元数据。 这个方法是真正加载数据的方法,其内容如下:
private boolean loadFSImage(FSNamesystem target, StartupOption startOpt,
MetaRecoveryContext recovery)
throws IOException {
final boolean rollingRollback
= RollingUpgradeStartupOption.ROLLBACK.matches(startOpt);
final EnumSet<NameNodeFile> nnfs;
if (rollingRollback) {
// if it is rollback of rolling upgrade, only load from the rollback image
nnfs = EnumSet.of(NameNodeFile.IMAGE_ROLLBACK);
} else {
// otherwise we can load from both IMAGE and IMAGE_ROLLBACK
nnfs = EnumSet.of(NameNodeFile.IMAGE, NameNodeFile.IMAGE_ROLLBACK);
}
final FSImageStorageInspector inspector = storage
.readAndInspectDirs(nnfs, startOpt);
isUpgradeFinalized = inspector.isUpgradeFinalized();
List<FSImageFile> imageFiles = inspector.getLatestImages();
StartupProgress prog = NameNode.getStartupProgress();
prog.beginPhase(Phase.LOADING_FSIMAGE);
File phaseFile = imageFiles.get(0).getFile();
prog.setFile(Phase.LOADING_FSIMAGE, phaseFile.getAbsolutePath());
prog.setSize(Phase.LOADING_FSIMAGE, phaseFile.length());
boolean needToSave = inspector.needToSave();
Iterable<EditLogInputStream> editStreams = null;
initEditLog(startOpt);
if (NameNodeLayoutVersion.supports(
LayoutVersion.Feature.TXID_BASED_LAYOUT, getLayoutVersion())) {
// If we're open for write, we're either non-HA or we're the active NN, so
// we better be able to load all the edits. If we're the standby NN, it's
// OK to not be able to read all of edits right now.
// In the meanwhile, for HA upgrade, we will still write editlog thus need
// this toAtLeastTxId to be set to the max-seen txid
// For rollback in rolling upgrade, we need to set the toAtLeastTxId to
// the txid right before the upgrade marker.
long toAtLeastTxId = editLog.isOpenForWrite() ? inspector
.getMaxSeenTxId() : 0;
if (rollingRollback) {
// note that the first image in imageFiles is the special checkpoint
// for the rolling upgrade
toAtLeastTxId = imageFiles.get(0).getCheckpointTxId() + 2;
}
editStreams = editLog.selectInputStreams(
imageFiles.get(0).getCheckpointTxId() + 1,
toAtLeastTxId, recovery, false);
} else {
editStreams = FSImagePreTransactionalStorageInspector
.getEditLogStreams(storage);
}
int maxOpSize = conf.getInt(DFSConfigKeys.DFS_NAMENODE_MAX_OP_SIZE_KEY,
DFSConfigKeys.DFS_NAMENODE_MAX_OP_SIZE_DEFAULT);
for (EditLogInputStream elis : editStreams) {
elis.setMaxOpSize(maxOpSize);
}
for (EditLogInputStream l : editStreams) {
LOG.debug("Planning to load edit log stream: " + l);
}
if (!editStreams.iterator().hasNext()) {
LOG.info("No edit log streams selected.");
}
Exception le = null;
FSImageFile imageFile = null;
for (int i = 0; i < imageFiles.size(); i++) {
try {
imageFile = imageFiles.get(i);
loadFSImageFile(target, recovery, imageFile, startOpt);
break;
} catch (IllegalReservedPathException ie) {
throw new IOException("Failed to load image from " + imageFile,
ie);
} catch (Exception e) {
le = e;
LOG.error("Failed to load image from " + imageFile, e);
target.clear();
imageFile = null;
}
}
// Failed to load any images, error out
if (imageFile == null) {
FSEditLog.closeAllStreams(editStreams);
throw new IOException("Failed to load FSImage file, see error(s) " +
"above for more info.");
}
prog.endPhase(Phase.LOADING_FSIMAGE);
if (!rollingRollback) {
long txnsAdvanced = loadEdits(editStreams, target, startOpt, recovery);
needToSave |= needsResaveBasedOnStaleCheckpoint(imageFile.getFile(),
txnsAdvanced);
if (RollingUpgradeStartupOption.DOWNGRADE.matches(startOpt)) {
// rename rollback image if it is downgrade
renameCheckpoint(NameNodeFile.IMAGE_ROLLBACK, NameNodeFile.IMAGE);
}
} else {
// Trigger the rollback for rolling upgrade. Here lastAppliedTxId equals
// to the last txid in rollback fsimage.
rollingRollback(lastAppliedTxId + 1, imageFiles.get(0).getCheckpointTxId());
needToSave = false;
}
editLog.setNextTxId(lastAppliedTxId + 1);
return needToSave;
}
这个方法很长而且很重要。这里我们需要逐步分析。 首先是第14行到第25行,这段代码主要在处理FSImage文件。 首先是第14行会调用storage类的readAndInspectDirs方法, 这个对象在FSImage实例化的同时会被创建,其创建时的代码如下:
这里storage的类型是NNStorage,创建的时候传入了三个参数, 第一个是conf,这个是hdfs的配置文件对象,第二个是imageDirs,这个是传 入FSImage的第二个参数,在文档(8)中解析过,这个参数是配置文件中 dfs.namenode.name.dir的值,这个值是用来指定FSImage文件的存储位置的, 第三个参数是dfs.namenode.shared.edits.dir与 dfs.namenode.edits.dir的值,这几个值是用来指定操作日志文件的存储位置。
NNStorage的构造方法如下:
public NNStorage(Configuration conf,
Collection<URI> imageDirs, Collection<URI> 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));
}
这个方法主要会调用setStorageDirectories方法将数据保存。
然后是loadFSImage方法在第14行调用的readAndInspectDirs 方法,这个方法内容如下:
FSImageStorageInspector readAndInspectDirs(EnumSet<NameNodeFile> fileTypes,
StartupOption startupOption) throws IOException {
Integer layoutVersion = null;
boolean multipleLV = false;
StringBuilder layoutVersions = new StringBuilder();
// First determine what range of layout versions we're going to inspect
for (Iterator<StorageDirectory> it = dirIterator(false);
it.hasNext();) {
StorageDirectory sd = it.next();
if (!sd.getVersionFile().exists()) {
FSImage.LOG.warn("Storage directory " + sd + " contains no VERSION file. Skipping...");
continue;
}
readProperties(sd, startupOption); // sets layoutVersion
int lv = getLayoutVersion();
if (layoutVersion == null) {
layoutVersion = Integer.valueOf(lv);
} else if (!layoutVersion.equals(lv)) {
multipleLV = true;
}
layoutVersions.append("(").append(sd.getRoot()).append(", ").append(lv).append(") ");
}
if (layoutVersion == null) {
throw new IOException("No storage directories contained VERSION information");
}
if (multipleLV) {
throw new IOException(
"Storage directories contain multiple layout versions: "
+ layoutVersions);
}
// If the storage directories are with the new layout version
// (ie edits_<txnid>) then use the new inspector, which will ignore
// the old format dirs.
FSImageStorageInspector inspector;
if (NameNodeLayoutVersion.supports(
LayoutVersion.Feature.TXID_BASED_LAYOUT, getLayoutVersion())) {
inspector = new FSImageTransactionalStorageInspector(fileTypes);
} else {
inspector = new FSImagePreTransactionalStorageInspector();
}
inspectStorageDirs(inspector);
return inspector;
}
这个方法主要作用是遍历这个对象创建时传入的存储目录,从该目录中 找到需要的文件。这里的操作与具体文件目录有关,namenode格式化成功后, 这个目录下的结构如下:
如上图所示,如果配置的目录名为namenode,那么在其格式化后 会创建一个目录,该目录下有一个current目录和一个in_use.lock文件, current目录下存储的全是文件,其中主要是fsimage_XX文件和edits_XX文件。 注意这里的XX是后文将要提到的txid,fsimage的txid和edits的txid是可以 相互对应起来的。
fsimage的txid表示在持久化fsimage时edits的txid,在启动 加载元数据的时候,会先加载fsimage文件,还原持久化该文件时的数据状态,然 后重新执行该txid之后的edits中记录的操作。
然后继续看loadFSImage方法,第18行这里会通过上述方法返回的 inspector获取到文件夹中的fsimage文件。
随后便是第29行的initEditLog方法,这个方法很重要,它会负责 初始化与操作日志相关的类。这里会分为两种情况,一种是操作日志直接写入本地 文件,还有一种是写入远端,按照之前文档的高可用配置,这里的远端是 journalnode集群(这个配置下会同时写本地和远端)。
同时需要说明的是hdfs的操作日志并不是直接写入一个文件,而是 为操作赋予txid,然后按txid分段存储。所以在读取操作日志的时候会有多个 日志流。
然后是第31行到第65行,在初始化了editlog后,需要获取到具 体的editlog文件。这段代码主要是分不同的情况来获取流文件。
然后是第67行到第90行,在准备好了fsimag和editlog后,便可 以开始加载元数据了。这段代码负责的是加载fsimag文件,重点在于第72行,这 里会使用for循环遍历所有的fsimage文件,然后从第一个开始执行 loadFSImageFile方法加载fsimage,若执行成功则退出,若失败则加载下一 个文件。
最后是第92行到末尾,加载fsimage之后的editlog。重点是93行 的loadEdits方法。这个方法会从上文提到的editlog的流中读取数据并加载到 内存中。