HDFS永久性数据结构

前言

本文主要讲解了HDFS是如何在磁盘上组织永久性数据的,主要从NameNode、Secondary NameNode和DataNode的目录结构以及fsimage和edits文件几个方面进行介绍,了解这些文件的用法有助于进行故障诊断和故障检出。

Namenode的目录结构

NameNode在被格式化之后,会产生如下的目录结构:

1
2
3
4
5
6
${dfs.name.dir}/
|---current/
|--- VERSION
|--- edits
|--- fsimage
|--- fstime
  • ${dfs.name.dir} 是HDFS配置文件的一个属性名,存储着镜像内容,推荐配置为一个NFS的挂载。

VERSION文件是一个Java属性文件,其中包含了正在运行的HDFS的版本信息,内容如下:

1
2
3
4
namespaceID=134368444
cTime=0
storageType=NAME_NODE
layoutVersion=-18
  • namespaceID属性是HDFS文件系统的唯一标识符,是在文件系统首次格式化(hadoop namenode -format)的时候设置的。datanode在注册到NameNode之前都不知道namespaceID的值,因此NameNode可以通过该属性来鉴别是否为新建的DataNode
  • cTime属性标记了NameNode的创建时间,对于刚格式化的存储系统,该值为0,;但在文件系统升级之后,该值会更新到新的时间戳
  • storageType属性说明该存储目录(${dfs.name.dir}/current/)
  • layoutVersion属性是一个负整数,描述HDFS持久性数据结构(也称布局)的版本号,但是与Hadoop发布包的版本号无关。只要HDFS布局变更,版本号变回递减(-1),此时,HDFS也需要升级。否则,磁盘仍然使用旧版本的布局,新版本的NameNode无法工作。

NameNode的存储目录中还包含edits、fsimage、和fstime等二进制文件。这些文件都使用Hadoop的Writable对象作为其序列化格式。

fsimage和edits 文件

HDFS客户端执行写操作(创建、移动文件)时,这些操作信息都会记录在edits文件中。NameNode在内存中维护HDFS的元数据;当edits日志被修改时,内存中的元数据也会同步更新,该元数据支持客户端的读请求。

每次执行写操作时,首先修改edits文件,并进行更新和同步。在NameNode向多个目录写数据时,只有所有的写操作均完毕后方才返回成功代码,以确保不会因为机器故障而丢失(所有操作都在edits文件中,可以在恢复时进行相应操作的重演,类似于write ahead log的思想)。

fsimage是HDFS元数据的一个永久性的checkpoint。由于fsimage很大(可高达几个GB),如果频繁写,会拖慢系统的运行,因此,并非每一次写操作都会更新该文件。在NameNode发生故障后,可以先将fsimage文件读入内存进行重构,然后执行edits中记录的各项操作,这也是NameNode启动阶段做的事情。

fsimage包含了HDFS中所有目录和文件inode的序列化信息。每个inode都是一个文件或目录的元数据内部描述。对文件,包含信息有”副本级别”(replication level)、修改时间和访问时间、访问许可、块大小、组成该文件的块等;对于目录,包含的信息有修改时间、访问许可和配额元数据等信息。

edits文件会无限增长,这虽然不会拖慢NameNode的运行,但会使得NameNode的启动异常耗时,有违用户的期待,因此,secondary NameNode应用而生。

Secondary NameNode辅助NameNode创建fsimage文件,它主要的功能有两个

  • 作为备份,在NameNode故障时升级为NameNode提供服务
  • 定期合并edits和fsimage文件,防止NameNode故障重启时,由于fsimage长时间没有同步,执行edits的恢复操作很耗时。

Secondary NameNode的工作步骤如下图所示

  1. SecondaryNameNode通知NameNode准备提交edits文件,此时主节点将新的写操作数据记录到一个新的文件edits.new中。
  2. SecondaryNameNode通过HTTP GET方式获取NameNode的fsimage与edits文件(在SecondaryNameNode的current同级目录下可见到 temp.check-point或者previous-checkpoint目录,这些目录中存储着从namenode拷贝来的镜像文件)。
  3. Secondary NameNode将获取的fsimage文件载入内存,并逐一执行edits文件的操作,产生新的fsimage文件fsimage.chpt
  4. SecondaryNameNode用HTTP POST方式发送fsimage.ckpt至NameNode。
  5. NameNode将fsimage.ckpt与edits.new文件分别重命名为fsimage与edits,然后更新fstime,记录检查点执行的时间,到此整个checkpoint过程到此结束。

最终,NameNode拥有一个更新的fsimage文件和一个更小的edits文件(在Secondary NameNode执行merge时,NameNode可能收到新的写请求)。在NameNode处在安全模式是,管理员可使用hadoop dfsadmin -saveNamespace命令主动创建checkpoint。

该过程解释了Secondary NameNode和NameNode拥有相近内存需求的原因,因为Secondary NameNode在合并时需要先将fsimage读入内存中。创建checkpoint的触发条件有两个限制:通常情况下,Secondary NameNode每个一小时(fs.checkpoint.period属性设置,以秒为单位)创建一次;此外,当edits文件达到64MB(fs.checkpoint.size属性设置,以字节为单位),即使没到一小时,也会创建checkpoint。系统每5分钟检查一次edits的大小。

Secondary NameNode的目录结构

创建checkpoint不仅为NameNode创建了checkpoint,还使得Secondary NameNode也有了一份备份数据(存储在previous.checkpoint目录中),此数据可用于NameNode元数据的备份

1
2
3
4
5
6
7
8
9
10
11
${fs.checkpoint.dir}/
|---current/
| |--- VERSION
| |--- edits
| |--- fsimage
| |--- fstime
|---previous.checkpoint/
|--- VERSION
|--- edits
|--- fsimage
|--- fstime

Secondary NameNode的previous.checkpoint/和current/与NameNode的current目录布局相同。在NameNode发生故障时,可以从Secondary NameNode恢复数据。有两种方法实现。

  1. 将相应目录复制到新的NameNode中
  2. 使用-importCheckpoint选项,启动NameNode守护进程,从而是Secondary NameNode用作新的NameNode。

DataNode的目录结构

与NameNode不同,DataNode的存储目录是初始阶段自动创建的,不需额外格式化。结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
${dfs.data.dir}/
|---current/
|--- VERSION
|--- blk_<id_1>
|--- blk_<id_1>.meta
|--- blk_<id_2>
|--- blk_<id_2>.meta
|--- ...
|--- blk_<id_64>
|--- blk_<id_64>.meta
|--- subdir0/
|--- subdir1/
|--- ...
|--- subdir63/

DataNode的VERSION文件与NameNode的VERSION文件类似,如下所示

1
2
3
4
5
namespaceID=134368444
storageID=DS-547717739-172.16.85.1-50010-1236720751627
cTime=0
storageType=DATA_NODE
layoutVersion=-18

namespaceID、cTime、layoutVersion都与NameNode中的值相同。namespaceID是DataNode首次访问NameNode时获取的。storageID对每个DataNode来说是唯一的(但对单个DataNode中所有存储目录来说则相同),NameNode可用该值区分不同DataNode。storageType表明该目录是DataNode的存储结构。

DataNode的current目录中其他文件都有blk_前缀,包括两种类型:HDFS块文件(仅包含原始数据)和块的元数据(.meta后缀)。块文件包含所存储文件的一部分原始数据(文件分块存储);元数据包括头部(含版本和类型)及该块各区段的一系列校验和。

当目录中数据块文件增加到一定规模,DataNode会创建一个子目录来存放新的块和元数据信息。如果当前目录存储到64个(由dfs.datanode.numblocks设置),就创建一个子目录。最终设计一个高扇出的目录树,即使快数量很多,但目录树的层数也不多。

如果dfs.data.dir属性指定不同磁盘上的多个目录,数据块会以轮转(round-robin)的方式写到各个目录中。同一个DataNode上的每个磁盘的块不重复,不同DataNode之间的块才可能重复(如三备份原则保证同一机架的两个DataNode备份,不同机架上有一个DataNode备份)