Hadoop Distributed File System(HDFS) 是一个分布式文件系统,可以运行在普通的计算机上(相对于专业的服务器)。它与现有的分布式文件系统有许多相似之处,也有不同于其他分布式文件系统的特性。HDFS 是具有高容错性的,并且可以应用于性能较低的计算机上。HDFS 提供了对应用程序数据的高吞吐量访问,并且适用于具有大型数据集的应用程序。HDFS 放松了一些 POSIX 需求,以使流访问文件系统数据。HDFS 最初是作为 Apache Nutch web 搜索引擎项目的基础设施而开发出来的。如今 HDFSApache Hadoop 核心项目的一部分。

关键特性

我们先简单浏览一下 HDFS 的一些关键特性。

  • 故障检测和快速恢复
  • 使用批处理而不是交互式处理提高了数据的吞吐量,而不考虑数据获取的低延迟
  • 适合处理大型文件(一个文件大小普遍为 GB 或 TB 级)
  • 使用简单一致性模型,一次写入多次读取操作,允许文件的创建、删除,只允许文件的添加和截取,不支持在文件任意位置随机更新文件,这种简单的一致性模型可以提供数据吞吐量。使其尤其适合批处理程序和网络爬虫
  • 可以让处理数据操作尽量发生在存储数据的节点附近,这样可以减少数据获取时间,提高程序计算效率
  • 移植性好

NameNode 和 DataNodes

HDFS 有一个主/从架构。HDFS 集群由一个 NameNode 和多个 DataNodes 组成,NameNode 是管理文件系统名称空间的主服务器,并管理客户对文件的访问。DataNodes通常是集群中的每个节点,它们管理连接到它们运行的节点的存储。

HDFS 对外公开文件系统名称空间,并允许将用户数据存储在文件中。在内部,文件被分割成一个或多个(block),这些存储在一组 DataNodes 中。

NameNode 执行文件系统命名空间操作,如打开、关闭和重命名文件和目录。它还决定了到 DataNodes 的映射。

DataNodes 负责响应文件系统的客户端的读写请求。DataNodes 还会接收 NameNode 发送的指令执行创建、删除和复制操作。

主从架构

NameNode 和 DataNode 都是是在后台运行的守护进程。HDFS 是使用 Java 语言构建的;任何支持 Java 的计算机都可以运行 NameNode 或 DataNode 守护进程。使用高度可移植的 Java语言意味着 HDFS 可以部署在大多数机器上。一般来说,NameNode 会运行在专门的计算机上,而集群中的其他计算机都会运行 DataNode 守护进程的一个实例。HDFS 架构允许在同一台计算机上运行多个 DataNodes,但是在实际部署中很少出现这种情况。

整个集群中只有一个 NameNode 负责管理整个文件系统的元数据,这极大地简化了系统的架构,同时用户数据基本不会经过 NameNode(除非在同一台计算机上部署了 DataNode)。

文件系统名称空间

HDFS支持传统的分层文件组织结构。用户或应用程序可以在这些目录中创建目录和存储文件。文件系统名称空间层次结构跟大多数现有的文件系统都很类似,支持创建和删除文件,将文件从一个目录移动到另一个目录,或者重命名文件。HDFS 还支持用户配额(user quotas)和访问权限(access permissions)。HDFS 不支持硬链接或软链接,但是不排除以后会实现这些特性的可能。

NameNode 负责维护文件系统名称空间。NameNode 会记录文件系统名称空间或其属性的任何更改。应用程序可以指定应该由 HDFS 维护的文件的副本数量。文件的副本数量称为该文件的复制因子(replication factor),该信息也是由 NameNode 存储。

数据复制

HDFS 用于在大型集群中的机器上可靠地存储非常大的文件。它将每个文件存储为块序列。为了能容错,每个文件的块都会被复制备份。每个文件的块大小复制因子都是可配置的。

每一个文件的的所有块(除了最后一个块之外)都是相同的大小,而用户可以在不填满最后一个块(填满成配置的块大小)的情况下再开始写一个新的块,在 appendhsync 已支持可变长度块之后。

应用程序可以指定文件的副本数量。复制因子可以在文件创建时指定,之后可以再更改。HDFS 中的文件是一次性写入的(除了附加和截断),而且任何时候都只能有一个写者(执行写操作的节点)。

NameNode 决定数据块的复制操作。它会定期接收集群中的每个 DataNode 的心跳和数据块报告。接收到心跳意味着 DataNode 能够正常工作。数据块报告包含一个 DataNode 上的所有块的列表。

下图表示一个具有 1 个 NameNode 和 8 个 DataNode 的集群,每个 DataNode 上都有自己的数据块,而 NameNode 会收集每个 DataNode 发送的数据块报告,并记录整个集群中的数据块情况,如数据块所属的文件,复制次数,数据块ID等。

数据复制

备份位置

大型的 HDFS 实例通常运行在分布在多个机架上的计算机集群上。不同机架间的两个节点间的通信必须通过交换机。在大多数情况下,同一机架中的机器之间的网络带宽大于不同机架间的网络带宽。

NameNode 可以通过 Hadoop Rack Awareness 判断每个 DataNode 属于哪个机架。一个简单都不是很理想的分配备份位置的策略就是把备份放在不同的机架上。这样可以避免整个机架出现故障时丢失数据,并且读取数据时也可以利用多个机架的带宽。该策略将数据备份均匀地分发到整个集群中,出现局部故障时也可以均衡负载,但是这也会增加写入数据的开销,因为一次写入需要在多个机架之间传输数据块。

针对通用的情况,当备份次数为 3 时,HDFS 的备份放置策略是将一个副本放在本地机架上的一个节点上,另一个备份放在本地机架上的另一个节点上,最后一个副本放在不同的机架上的另一个节点上。这一策略减少了机架间的写入流量,一般都会提高写入性能。又因为整个机架故障的可能性远小于单个节点故障,所以该策略对数据可靠性和可用性保证影响不大。但是,它确实减少了读取数据时使用的整体网络带宽,因为块最终被放置在两个不同的机架上而不是三个,而且文件的副本也不会均匀分布在各个机架上。三分之二的副本在同一个机架上,另外三分之一在其他机架上均匀分布。但我们也得看到,这一策略明显提高了写入性能,还基本不影响数据的可靠性或读取性能。

如果备份次数大于 3,那么第 4 个副本及之后的副本的位置会随机选择,但是每个机架存放的副本数量必须不能超过一个上限(一般来说是 (副本总数 - 1) / 机架总数 + 2)。

因为 NameNode 不允许一个 DataNode 上存储同一个数据的多个副本,因此数据最大备份数量等于当时可用的 DataNode 的数量。

HDFS 还打算引入存储类型和存储策略:NameNode 会先基于之前描述的机架感知策略放置数据副本,然后检查候选节点的存储类型是否满足存储文件的存储策略所要求的存储类型,如果候选节点不满要求,就查找下一个节点。当第一次遍历完所有的数据节点都没有找到符合要求的节点时,那么 NameNode 会再下一次遍历时查找满足回退(fallback)的存储类型的节点。截至撰写本文时,该功能还未完成。

副本选择

为了降低整体带宽开销与读取延迟,当某个节点请求读取数据时,HDFS 会在所有存放数据备份的节点里选择离该节点最近的一个节点提供数据备份。比如,如果某个备份节点与读取节点处于同一个机架上,那么这个节点相比于其他机架上的备份节点,就会优先选择该节点;如果 HDFS 部署在多个数据中心上,那么相比于比远程数据中心的备份节点,就会优先选择本地数据中心的备份节点。

安全模式

HDFS 启动时,NameNode 会进入安全模式。在安全模式下,NameNode 不允许进行数据块复制操作。NameNode 会接受 DataNode 发送的数据块报告,数据块报告里包含了 DataNode 上所有数据块的列表。每个数据块都有它的最小副本数,当 NameNode 检查数据块的副本数不低于最小副本数时,就认为该数据块可以执行安全复制。当 NameNode 检查通过的数据块达到一定的比例(可配置)时,再过 30 秒之后,NameNode 就会推出安全模式。之后 NameNode 会找出那些副本数仍然低于最小副本数的数据块,然后把这些数据块备份到其他 DataNode。

文件系统元数据的存储

HDFS 的名称空间是由 NameNode 进行维护。NameNode 使用一种称为 EditLog 的数据处理日志来保存每次对文件系统元数据的修改记录。比如,创建一个新文件,NameNode 就会在 EditLog 里插入一条代表插入的记录。同样地,修改一个文件的备份次数也会导致 NameNode 在 EditLog 中插入一条对应的记录。NameNode 会使用本地操作系统来保存 EditLog 成一个文件。

整个操作系统的名称空间,包括数据块到文件的映射关系以及文件系统的属性,都保存在一个叫做 FsImage 的文件里,这个文件也是保存在本地操作系统的文件操作系统中。

NameNode 在内存中保存整个文件系统名称空间和文件块映射的映像。当 NameNode 启动,或者检查点满足配置的触发条件时,它就从磁盘读取 FsImageEditLog,将 EditLog里记录的数据处理操作都应用到内存里的 FsImage,然后将更新后的 FsImage 写入到磁盘上的新的 FsImage 中。然后,它可以截断旧的 EditLog,因为它的数据处理操作已经应用到保存在磁盘上的 FsImage 中了。这个过程称为检查点(checkpoint)。检查点的目的是确保 HDFS 通过获取文件系统元数据的快照并将其保存为FsImage,从而保证文件系统元数据具有统一的视图。尽管读取一个 FsImage 的效率比较高,但是直接对 FsImage 进行增量编辑却没那么高效了。我们不会在每次编辑操作发生的时候就编辑 FsImage,而是将编辑记录保存在 Editlog 中。在检查点期间,再将Editlog中记录的更改应用到 FsImage。一个检查点可以在给定的时间间隔(dfs.namenode.checkpoint.period,单位为秒)触发,或在文件系统编辑操作积累到指定的次数(dfs.namenode.checkpoint.txns)时触发。如果这两个属性都设置了,那么满足任意一个条件都会将触发检查点。

DataNode 把 HDFS 的数据存储在本地文件系统的文件中,因为 DataNode 并不能识别 HDFS 文件中的文件。它会把每个数据块都单独存一个文件,但它不会将所有的文件都存放在同一个目录下,当它觉得当前目录不应该再继续存放文件时,就会创建一个子目录来存放文件。把所有的文件都放在一个目录下往往是不理想的,因为本地操作系统不支持一个目录中存放太多的文件。

当 DataNode 守护进程启动时,就会扫描本地文件系统里的所有文件,然后生成一个数据块与本地文件系统里的文件的映射表,然后发送给 NameNode。这个就是之前提到的数据块报告

数据结构

数据块

HDFS 使用的数据块大小一般是 128MB。

缓存

客户端创建新文件的请求并不会立即就发送给 NameNode,而是保存在本地缓存中,当累计到一个数据块大小时,才会发送给 NameNode,NameNode 将该文件插入到文件系统树中,并进行调度,找到 DataNodes 中可用的数据块,然后将 DataNode 和数据块编号发送给客户端。客户端接受到 NameNode 的响应后,把数据刷新(flush)到 DataNode 的数据块中。当文件关闭时,本地缓存里还没刷新的数据也会发送到 DataNode。然后客户端会通知 NameNode 文件已经关闭了。这时,NameNode 才会把文件编辑操作记录到 EditLog 中。如果 NameNode 在文件关闭之间就终止了,那么文件的数据就丢失了。

复制流水线

当设置了数据备份为 3 次(多次)时,客户端的本地缓存满了之后,就会得到 NameNode 发来的 DataNode 列表,然后给第一个 DataNode 分片地发送数据,而第一个 DataNode 接收第一片数据之后,就把第一片的数据发给第二个 DataNode(同时接收客户端发送过来的第二片数据),同理,第二个 DataNode 也会对第三个 DataNode 执行同样的操作。这样就构成了一条流水线。

访问 HDFS

HDFS 提供了以下访问方式:

  • FileSystem Java API
  • Java API 的 C 语言包装 API
  • REST API
  • HTTP 浏览器
  • NFS getaway 挂载到客户端的本地文件系统

FS Shell

HDFS 提供了一个名为 FS shell 的命令行接口,让用户与 HDFS 中的数据交互。这个命令集的语法跟其他 shell (如bash、csh) 类似。

操作 命令
创建目录 /foodir bin/hadoop dfs -mkdir /foodir
删除目录 /foodir bin/hadoop fs -rm -R /foodir
查看文件 /foodir/myfile.txt bin/hadoop dfs -cat /foodir/myfile.txt

DFSAdmin

DFSAdmin 命令是用来管理 HDFS 集群,一般由集群管理员使用。

操作 命令
打开安全模式 bin/hdfs dfsadmin -safemode enter
生产 DataNode 列表 bin/hdfs dfsadmin -report
启动或停止 DataNode bin/hdfs dfsadmin -refreshNodes

空间回收

文件删除和撤销删除

如果配置了回收站属性的话,通过 FS Shell 移除的文件不会立即被删除,HDFS 会将删除的文件放到回收站目录(每个用户都有自己的回收站目录 /user/<username>/.Trash)。

减少了备份次数

当减少了备份次数之后,NameNode 会选择有多余的备份 DataNode,在下一次心跳发送消息给 DataNode,让他删除多余的数据块。在调用 setReplication API 到实际删除备份之间存在一定的延迟。