HDFS中的NameNode、DataNode、Secondery NameNode是如何在磁盘上组织和存储持久化数据的?下面将分别进行介绍。

注意,这里主要介绍的是Hadoop 2.0以前的版本,Hadoop 2.0以后版本文件结构稍微有一些变化,因为目前我们还没有使用hadoop 2.0,所以后面只是稍微说一下hadoop 2.0中NameNode目录结构,其他有兴趣的可以自己再去深入的研究。

NameNode的文件结构

最新格式化的NameNode会创建以下目录结构:

${dfs.name.dir}/current/VERSION
                                        /edits
                                        /fsimage
                                        /fstime

其中dfs.name.dir属性是一个目录列表,是每个目录的镜像。这个机制使系统具备了一定的复原能力,特别是当其中一个目录位于NFS之上时。

VERSION文件是Java属性文件,其中包括运行HDFS的版本信息,下面是一个典型的VERSION文件包括的内容:

#Tue Jun 17 16:55:18 CST 2014
namespaceID=1812798012
cTime=0
storageType=NAME_NODE
layoutVersion=-18

其中,namespaceID是文件系统的唯一标识符。在文件系统第一次被格式化时便会创创建namespaceID。这个标识符也要求各DataNode节点和NameNode节点保持一致。NameNode会使用此标识符识别新的DataNode。DataNode只有在向NameNode注册后才会获得此namespaceID。cTime属性标记了NameNode存储空间创建的时间。对于新格式化的存储空间,虽然这里的cTime属性值为0,但是只要文件系统被更新,它就会更新到一个新的时间戳。StorageType用于指出此存储目录包含一个NameNode的数据结构,在DataNode中它的属性值为DataNode。

layoutVersion是一个负的整数,定义了HDFS持久数据结构(也称布局)的版本。注意,该版本号和Hadoop的发行版本号无关。每次HDFS的布局发生变化,该版本号就会递减(比如-18版本号之后是-19),在这种情况下,HDFS就需要更新升级,因为如果一个新的NameNode或DataNode还处于旧版本上,那么系统就无法正常运行,各节点版本号要保持一致。

NameNode的存储目录包含edits、fsimage、fstime三个文件。它们都是二进制的文件,可以通过HadoopWritable对象进行序列化。下面将介绍NameNode的工作原理,以便让大家更清晰的理解这三个文件的作业。

编辑日志(edit log)及文件系统映像(filesystem image)

当客户端执行写操作时,NameNode会先在编辑日志中写下记录,并在内存中保存一个文件系统元数据,元数据会在编辑日志有所改动后进行更新。内存中的元数据用来提供读数据请求服务。

编辑日志会在每次成功操作之后、成功代码尚未返回给客户端之前进行刷新和同步。对于要写入多个目录操作,写入流要刷新和同步到所有的副本,这就保证了操作不会因故障而丢失数据。

fsimage文件是文件系统元数据的持久性检查点。和编辑日志不同,它不会在每个文件系统的写操作之后都进行更新,因为写出fsimage文件会非常慢(fsimage可能增长到GB大小。)这种设计并不影响系统的恢复力,因为如果NameNode失败,那么元数据的最新状态可以通过将磁盘中读出的fsimage文件加载到内存中来进行重建恢复,然后重新执行编辑日志中的操作。事实上,这也正是NameNode启动时要做的事情。一个fsimage文件包含以序列化格式存储的文件系统目录和文件inodes。每个inodes表示一个文件或目录的元数据信息,以及文件的副本数、修改和访问时间等信息。

正如上面所描述的,Hadoop文件系统会出现编辑日志不断增长的情况。尽管在NameNode运行期间不会对系统造成影响,但是,如果NameNode重新启动,它将会花费很多时间运行编辑日志中的每个操作。在此期间(即安全模式时间),文件系统还是不可用的,通常来说还是不符合应用需求。

为了解决这个问题,Hadoop在NameNode之外的节点上运行一个Secondary NameNode进程。它的任务就是为原NameNode内存中的文件系统元数据产生检查点。下面我们参照图10对检查点处理过程进行描述。

hdfs目录下文件数 hdfs目录结构_java

                                    图 10 检查点处理过程

1)Secondary NameNode首先请求原NameNode进行edits的滚动,这样新的编辑操作就能够进入新的文件中。

2)Secondary NameNode通过HTTP方式读取原NameNode中的fsimage及edits。

3)Secondary NameNode读取fsimage到内存中,然后执行edits中的每个操作,并创建一个新的统一的fsimage文件。

4)Secondary NameNode(通过HTTP方式)将新的fsimage发送到原NameNode。

5)原NameNode用新的fsimage替换旧的fsimage,旧的edits文件通过步骤1)中的edits进行替换。同时系统会更新fsimage文件到记录检查点记录的时间。

在这个过程结束后,NameNode就有了最新的fsimage文件和更小的edits文件。事实上,对于NameNode在安全模式的这种情况,管理员可以通过以下命令运行这个过程:

Hadoop dfsadmin –saveNamespace

这个过程清晰的表明了Secondary NameNode要有和原NameNode一样的内存需求的原因—要把fsimage加载到内存中,因此Secondary NameNode在集群中也需要有专用机器。但hadoop的默认配置中让snn进程默认运行在namenode的那台机器上,但我们并不建议这么做。

有关检查点的时间表由两个配置参数决定。Secondary NameNode每个小时会插入一个检查点(fs.checkpoint.period , 以秒为单位),如果编辑日志达到64MB(fs.checkpoint.size,以字节为单位),则间隔时间更短,每隔5分钟会检查一次。

 

Secondary NameNode的目录结构

Secondary NameNode在每次处理过程结束后都会有个检查点。这个检查点可以在一个子目录/previous.checkpoint中找到,可以作为NameNode的元数据备份源,目录如下:

${fs.checkpoint.dir}/current/VERSION
                                        /edits
                                        /fsimage
                                        /fstime
                              /previous.checkpoint/VERSION
                                         /edits
                                         /fsimage
                                         /fstime

以上这个目录和Secondary NameNode的/current目录结构是完全相同的。这样设计的目的是:万一整个NameNode发生故障,并且没有用于恢复的备份,甚至NFS中也没有备份,就可以直接从Secondary NameNode恢复。具体方式有两种,第一种是直接复制相关的目录到新的NameNode中。第二种是在启动NameNode守护进程时,Secondary NameNode可以使用-importCheckPoint选项,并作为新的NameNode继续运行任务。-importCheckPoint选项将加载fs.checkpoint.dir属性定义的目录中的最新检查点的NameNode数据,但这种操作只有在dfs.name.dir所指定的目录下没有元数据的情况下才进行,这样就避免了重写之前元数据的风险。

DataNode的目录结构

DataNode不需要进行格式化,它会在启动时自己创建存储目录,其中关键的文件和目录如下:

${dfs.data.dir}/current/VERSION
                                         /blk_<id_1>
                                         /blk_<id_1>.meta
              /blk_<id_2>
              /blk_<id_2>.meta
              ……
              /subdir0/
              /subdir1/
              /…
              /subdir63/

DataNode中的VERSION文件跟NameNode类似,

#Wed Jun 18 19:32:42 CST 2014
namespaceID=1812798012
storageID=DS-1146216163-10.16.78.34-50010-1400812209946
cTime=0
storageType=DATA_NODE
layoutVersion=-18

其中namespaceID、cTime和layoutVersion值与NameNode中的都一样,namespaceID在第一次连接NameNode时就会从中获取。storageID相对于DataNode来说是唯一的,用于在NameNode处标识DataNode。storageType将这个目录标志位DataNode数据存储目录。

DataNode中current目录下的其他文件都有blk_前缀,它有两种类型:

1)HDFS中的文件块本身,存储的是原始文件内容。

2)块的元数据信息(使用.meta后缀标识)。一个文件块由存储的原始文件字节组成,元数据文件由一个包含版本和类型信息的头文件和一系列块的区域校验和组成。

当目录中存储的块数据量增加到一定规模时,DataNode会创建一个新的目录,用于保存新的块及元数据。当目录中的块数据量达到64(可由dfs.DataNode.numblocks属性确定)时,便会新建一个子目录,这样就会形成一个更宽的文件树结构,避免了由于存储大量数据块而导致目录很深,使检索性能免受影响。通过这样的措施,数据节点可以确保每个目录中的文件块都可控的,也避免了一个目录中存在过多文件。

Hadoop 2.x版本文件结构

Hadoop 2.x版本文件结构跟之前的版本差别不是很大,以下我大概列一下我知道的不同的地方:

NameNode元数据结构如下:

${dfs.name.dir}/current/VERSION
                                        /edits
                                        /fsimage
                                        /seen_txid

VERSION 内容如下:只是比版本1多了一个clusterID和blockpoolID.clusterID是系统生成或手动指定的集群ID。blockpoolID是针对每一个Namespace所对应的blockpool的ID,这个ID包括了其对应的NameNode节点的ip地址。

#Tue Jun 17 16:55:18 CST 2014
namespaceID=1812798012
clusterID=cluster21
cTime=0
storageType=NAME_NODE
blockpoolID=BP-178902860-10.16.78.24-1400812199650
layoutVersion=-40

另外这个目录下这个seen_txid文件替换了fstime,seen_txid在hadoop2.x被用来是存放transactionId的文件,format之后是,它代表的是namenode里面的edits_*文件的尾数,namenode重启的时候,会按照seen_txid的数字,循序从头跑edits_0000001~到seen_txid的数字。所以当你的hdfs发生异常重启的时候,一定要比对seen_txid内的数字是不是你edits最后的尾数,不然会发生建置namenode时metaData的资料有缺少,导致误删Datanode上多余Block的资讯。

其他的这里就不再多说,有兴趣的可以自己再去研究

 

参考资料:Hadoop实战 第2版 陆嘉恒著