一、概述
这篇blog的目的主要是为了对hadoop1.x和hadoop2.x的元数据运行机制进行比较。
二、fsimage和edits文件的作用
先来看看关于NameNode元数据相关的目录结构,也就是配置在hdfs-site.xml上的dfs.name.dir项,具体目录为$dfs.name.dir/current。看看目录(hadoop2.2.0版本):
我们发现有些以edites_开头和少量以fsimage开头的文件。fsimage和edites文件都是hadoop文件系统元数据的组成部分。
其中fsimage镜像文件包含了整个HDFS文件系统的所有目录和文件的indoe信息。对于文件来说包括了数据块描述信息、修改时间、访问时间等;对于目录来说包括修改时间、访问权限控制信息(目录所属用户,所在组等)等。
另外,edit文件主要是在NameNode已经启动情况下对HDFS进行的各种更新操作进行记录,HDFS客户端执行所有的写操作都会被记录到edit文件中。
三、NameNode简单启动过程
在 HDFS中,任何一个文件,目录和block,在HDFS中都会被表示为一个object存储在namenode的内存中,每一个object占用150 bytes的内存空间。当NameNode启动的时候,首先会将fsimage里面的所有内容映像到内存中,然后再一条一条地执行edits中的记录,然 后等待各个Datanode向自己汇报块的信息来组装blockMap,从而离开安全模式。在这里涉及到BlockMap结构,所谓的BlockMap结 构就是记录着block的元数据(加载在NameNode的内存中)和其对应的实际数据(存储在各个DataNode中)的映射关系。真正每个block 对应到datanodes列表的信息在hadoop中并没有进行持久化存储,而是在所有datanode启动时,每个datanode对本地磁盘进行扫 描,将本datanode上保存的block信息汇报给namenode,namenode在接收到每个datanode的块信息汇报后,将接收到的块信 息,以及其所在的datanode信息等保存在内存中。HDFS就是通过这种块信息汇报的方式来完成 block -> datanodes list的对应表构建。Datanode向namenode汇报块信息的过程叫做blockReport,而namenode将block -> datanodes list的对应表信息保存在一个叫BlocksMap的数据结构中。因此,我们可以得出一个非常重要的结论,NameNode不会定期的向各个DataNode去”索取“块的信息,而是各个datanode定期向namenode汇报块的信息。当 组装完NameNode组装完BlockMap的信息后基本上整个HDFS的启动就完成了,可以顺利地离开安全模式了。分析到这里,我们就可以很清楚地知 道整个HDFS的启动速度是由上面决定的了,第一:执行各个edits文件,这个也是我这篇blog重点讨论的。第二:各个DataNode向 NameNode汇报块信息的进度(当99.9%的block汇报完毕才会离开安全模式)。
四、Hadoop1.x中fsimage和edits的合并机制
当edits文件很多很大的时候,NameNode在启动的时候需要逐一每条的执行这些edits文件,这就严重地影响了整个HDFS的启动时间。这问题 在hadoop1.x是通过SecondaryNamenode机制将edits文件合并到fsimage中,其之得到解 决,SecondaryNamenode在第一代的Hadoop中算是一个非热备的NameNode备份。整个SecondaryNamenode的工作 流程简单地画了一下图:
简单描述一下具体流程:
步骤一:SSN在一个checkpoint时间点和NameNode进行通信,请求NameNode停止使用edits文件记录相关操作而是暂时将新的Write操作写到新的文件edit.new来。
步骤二:SSN通过HTTP GET的方式从NameNode中将fsimage和edits文件下载回来本地目录中。
步骤三:SSN中合并edits和fsimage。SSN将从NameNode中下载回来的fsimage加载到内存中,然后逐条 执行edits文件中的各个操作项,使得加载到内存中的fsimage中包含edits中的操作,这个过程就是所谓的合并了。
步骤四:在SSN中合并完fsimage和edits文件后,需要将新的fsimage回传到NameNode上,这个是通过HTTP POST方式进行的。
步骤五:NameNode将从SSN接收到的新的fsimage替换掉旧的fsimage。同时将edits.new文件转换为通常的edits文件,这样edits文件的大小就得到减少了。SSN整个合并以及和NameNode的交互过程到这里已经结束。
五、Hadoop2.x中fsimage和edits的合并机制
(1)Hadoop2.x的HA策略简介
由于HDFS2.0的HA策略的加入,使得在hadoop2.x中的fsimage和edits的合并机制和hadoop1.x完全不同。在 hadoop2.x中已经没有SecondaryNamenode,而是直接通过QJM方式配置若干奇数个JournalNode来实现NameNode 热备HA策略。详细的Hadoop2.x的HA策略的原理和部署这里就不说了这里主要说说简单的HA机制以及其工作流程。在同一个集群当中同时运行着2个Namenode,一个处于Active状态,用于处理客户端的请求。另外 一个处于standy状态,用于热备,其状态和active Namenode是维持一致的,当Active Namenode出现故障,Standy Namenode可以马上转化为Active Namenode。但是 2个Namenode中有且只有一个处于active状态来处理客户端的请求,否则将会产生脑裂情况。这样看来,那么客户端的一次写请求,其操作日志需要 同时被记录再Active NameNode和standy NameNode中。那么疑问产生了,在保证不产生脑裂的情况下如何使得操作日志需要同时被记录再Active NameNode和standy NameNode呢?
为了让Standby NameNode的状态和Active NameNode保持同步,即元数据保持一致,它们都将会和JournalNodes守护进程通信。当Active NameNode执行任何有关命名空间的修改,它至少需要将产生的edits持久化到N-((N-1)/2)个JournalNodes上才能保证命名空 间修改的安全性,换句话说:如果你的HA策略中启动了N个JournalNode进程那么整个QJM最多允许(N-1)/2个进程死掉,这样才能保证 editLog成功完整地被写入。比如 3个 JournalNode 时,最多允许 1 个 JournalNode挂掉,5个 JournalNode 时,最多允许 2 个 JournalNode 挂掉。而Standby NameNode负责观察edits log的变化,它能够读取从JNs中读取edits信息,并更新其内部的命名空间。一旦Active NameNode出现故障,Standby NameNode将会保证从JNs中读出了全部的Edits,然后切换成Active状态。Standby NameNode读取全部的edits可确保发生故障转移之前,是和Active NameNode拥有完全同步的命名空间状态。
(2)Hadoop2.x中fsimage和edits的合并流程
步骤一:Active Namenode和Standby NameNode从JournalNodes的edits共享目录中同步edits到自己edits目录中。其中JournalNodes的edits共享目录的共享目录在配置HA策略的时候由下面参数配置:
<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>qjournal://XX/xxcluster</value>
</property>
<property>
<name>dfs.journalnode.edits.dir</name>
<value>/journalNode/edits</value>
</property>
步骤二:Standby NameNode定期检查合并的条件是否成立,如果成立会合并fsimage和edits文件;
在Standby NameNode中会一直维护着一个叫CheckpointerThread的线程,这个线程调用StandbyCheckpointer类去每隔 1000*Math.min(checkpointCheckPeriod, checkpointPeriod)秒检测一次是否要将fsimage和从journalNode同步过来的edits做一次合并操作,其中 checkpointCheckPeriod由hdfs-site.xml中的dfs.namenode.checkpoint.period 配置,checkpointPeriod则有hdfs-site.xml中的dfs.namenode.checkpoint.check.period 配置。
<property>
<name>dfs.namenode.checkpoint.period</name>
<value>3600</value>
<description>The number of seconds between two periodic checkpoints.
</description>
</property>
<property>
<name>dfs.namenode.checkpoint.check.period</name>
<value>60</value>
<description>The SecondaryNameNode and CheckpointNode will poll the NameNode
every 'dfs.namenode.checkpoint.check.period' seconds to query the number
of uncheckpointed transactions.
</description>
</property>
其中具体什么条件才符合合并条件,我们就看看看StandbyCheckpointer类的dowork方法,看我的注释就一目了然了:
private void doWork() {
// Reset checkpoint time so that we don't always checkpoint
// on startup.
lastCheckpointTime = now();
while (shouldRun) {
try {
//每隔1000*Math.min(checkpointCheckPeriod, checkpointPeriod)秒检测一次是否要将fsimage和从journalNode同步过来的edits做一次合并操作
Thread.sleep(1000 * checkpointConf.getCheckPeriod());
} catch (InterruptedException ie) {
}
if (!shouldRun) {
break;
}
try {
// We may have lost our ticket since last checkpoint, log in again, just in case
if (UserGroupInformation.isSecurityEnabled()) {
UserGroupInformation.getCurrentUser().checkTGTAndReloginFromKeytab();
}
long now = now();
//获得最后一次往journalNode写入的TxId(这个可以在namenode日志或者50070界面可以看到)和最近一次做Checkpoint的TxId的差值
long uncheckpointed = countUncheckpointedTxns();
long secsSinceLast = (now - lastCheckpointTime)/1000;
boolean needCheckpoint = false;
//第一种符合合并的情况:当最后一次往journalNode写入的TxId(这个可以在namenode日志或者50070界面可以看到)
//和最近一次做Checkpoint的TxId的差值大于或者等于dfs.namenode.checkpoint.txns配置的数量(默认1000000)时做一次合并
if (uncheckpointed >= checkpointConf.getTxnCount()) {
LOG.info("Triggering checkpoint because there have been " +
uncheckpointed + " txns since the last checkpoint, which " +
"exceeds the configured threshold " +
checkpointConf.getTxnCount());
needCheckpoint = true;
}
//第二种符合合并的情况:当时间间隔大于或者等于dfs.namenode.checkpoint.period配置的时间是做合并
else if (secsSinceLast >= checkpointConf.getPeriod()) {
LOG.info("Triggering checkpoint because it has been " +
secsSinceLast + " seconds since the last checkpoint, which " +
"exceeds the configured interval " + checkpointConf.getPeriod());
needCheckpoint = true;
}
synchronized (cancelLock) {
if (now < preventCheckpointsUntil) {
LOG.info("But skipping this checkpoint since we are about to failover!");
canceledCount++;
continue;
}
assert canceler == null;
canceler = new Canceler();
}
if (needCheckpoint) {
doCheckpoint();
lastCheckpointTime = now;
}
} catch (SaveNamespaceCancelledException ce) {
LOG.info("Checkpoint was cancelled: " + ce.getMessage());
canceledCount++;
} catch (InterruptedException ie) {
// Probably requested shutdown.
continue;
} catch (Throwable t) {
LOG.error("Exception in doCheckpoint", t);
} finally {
synchronized (cancelLock) {
canceler = null;
}
}
}
}
}
步骤三:Standby NameNode中的StandbyCheckpointer类合并完fsimage和edits之后,将合并之后的fsimage上传到Active NameNode相应目录中;
步骤四:Active NameNode接到最新的fsimage文件之后,替换掉旧的fsimage和清理掉edits文件;到这里整个合并过程已经完毕。
五、总结
本文主要做了hadoop1.x和hadoop2.x的fsimage和edits文件合并机制的对比,另外也对hadoop2.x的HA机制做了简单的 介绍。写完这边blog下来自己对SSN以及hadoop2.x的edits文件的处理有了更深入清晰的认识;另外通过这样的对比,感觉自己收获最大的是 对整个HDFS的启动过程有很深入的认识。