HDFS 架构

简介

Hadoop 分布式文件系统 (HDFS) 是一种分布式文件系统,设计用于在商用硬件上运行。它与现有的分布式文件系统有很多相似之处。然而,它与其他分布式文件系统的区别也很显著。HDFS 具有很高的容错能力,并且设计为部署在低成本硬件上。HDFS 提供对应用程序数据的高吞吐量访问,并且适用于具有大型数据集的应用程序。HDFS 放宽了一些 POSIX 要求,以支持对文件系统数据的流式访问。HDFS 最初是作为 Apache Nutch 网络搜索引擎项目的架构而构建的。HDFS 是 Apache Hadoop Core 项目的一部分。项目 URL 为 https://hadoop.apache.org/

假设和目标

硬件故障

硬件故障是常态,而非例外。一个 HDFS 实例可能由数百或数千台服务器组成,每台服务器存储文件系统数据的一部分。组件数量庞大,并且每个组件都有非平凡的故障概率,这意味着 HDFS 的某个组件始终处于非功能状态。因此,故障检测和快速自动恢复是 HDFS 的核心架构目标。

流式数据访问

在 HDFS 上运行的应用程序需要对数据集进行流式访问。它们不是通常在通用文件系统上运行的通用应用程序。HDFS 更适合批量处理,而不是用户交互式使用。重点在于数据访问的高吞吐量,而不是数据访问的低延迟。POSIX 施加了许多硬性要求,而这些要求对于针对 HDFS 的应用程序来说是不需要的。为了提高数据吞吐率,在几个关键领域对 POSIX 语义进行了权衡。

大型数据集

在 HDFS 上运行的应用程序具有大型数据集。HDFS 中的典型文件大小为千兆字节到太字节。因此,HDFS 经过调整以支持大文件。它应该提供高聚合数据带宽,并且可以扩展到单个集群中的数百个节点。它应该在一个实例中支持数千万个文件。

简单一致性模型

HDFS 应用程序需要一个文件一次写入多次读取的访问模型。一个文件一旦创建、写入和关闭,就不需要更改,除了追加和截断。支持将内容追加到文件末尾,但不能在任意点更新。此假设简化了数据一致性问题,并支持高吞吐量数据访问。MapReduce 应用程序或网络爬虫应用程序完全符合此模型。

“移动计算比移动数据更便宜”

如果应用程序请求的计算在其操作数据附近执行,则效率会高得多。当数据集很大时尤其如此。这最大程度地减少了网络拥塞,并提高了系统的整体吞吐量。假设通常最好将计算迁移到数据所在位置附近,而不是将数据移动到应用程序运行的位置。HDFS 为应用程序提供了接口,以便将自身移到数据所在位置附近。

跨异构硬件和软件平台的可移植性

HDFS 被设计为可以轻松地从一个平台移植到另一个平台。这促进了 HDFS 作为大量应用程序的首选平台的广泛采用。

NameNode 和 DataNode

HDFS 具有主/从架构。HDFS 集群由一个 NameNode(一个管理文件系统名称空间并调节客户端对文件访问的主服务器)组成。此外,还有许多 DataNode(通常每个集群节点一个),它们管理运行所在节点附加的存储。HDFS 公开文件系统名称空间,并允许将用户数据存储在文件中。在内部,一个文件被分成一个或多个块,并且这些块存储在 DataNode 集合中。NameNode 执行文件系统名称空间操作,如打开、关闭和重命名文件和目录。它还确定块到 DataNode 的映射。DataNode 负责处理来自文件系统客户端的读取和写入请求。DataNode 还根据 NameNode 的指令执行块创建、删除和复制。

HDFS Architecture

NameNode 和 DataNode 是设计为在商用机器上运行的软件。这些机器通常运行 GNU/Linux 操作系统 (OS)。HDFS 使用 Java 语言构建;任何支持 Java 的机器都可以运行 NameNode 或 DataNode 软件。使用高度可移植的 Java 语言意味着 HDFS 可以部署在各种机器上。典型的部署有一个专门的机器,仅运行 NameNode 软件。集群中的每台其他机器运行一个 DataNode 软件实例。该架构不排除在同一台机器上运行多个 DataNode,但在实际部署中这种情况很少见。

集群中存在单个 NameNode 极大地简化了系统的架构。NameNode 是所有 HDFS 元数据的仲裁者和存储库。该系统设计为用户数据永远不会通过 NameNode 流动的方式。

文件系统命名空间

HDFS 支持传统的层次文件组织。用户或应用程序可以在这些目录内创建目录和存储文件。文件系统命名空间层次结构类似于大多数其他现有文件系统;可以创建和删除文件、将文件从一个目录移动到另一个目录或重命名文件。HDFS 支持 用户配额访问权限。HDFS 不支持硬链接或软链接。但是,HDFS 架构不排除实现这些功能。

虽然 HDFS 遵循 文件系统的命名约定,但某些路径和名称(例如 /.reserved.snapshot )是保留的。诸如 透明加密快照 等功能使用保留路径。

NameNode 维护文件系统命名空间。文件系统命名空间或其属性的任何更改都由 NameNode 记录。应用程序可以指定 HDFS 应维护的文件副本数。文件的副本数称为该文件的复制因子。此信息由 NameNode 存储。

数据复制

HDFS 设计为在大型集群中的机器上可靠地存储非常大的文件。它将每个文件存储为一系列块。文件的块被复制以实现容错。块大小和复制因子可针对每个文件进行配置。

文件中的所有块(最后一个块除外)大小相同,而用户可以在向追加和 hsync 添加可变长度块的支持后,在不将最后一个块填充到配置的块大小时启动一个新块。

应用程序可以指定文件的副本数。可以在文件创建时指定复制因子,并且可以在以后更改。HDFS 中的文件是一次写入(追加和截断除外),并且任何时候都只有一个写入者。

NameNode 对块复制做出所有决策。它定期从集群中的每个 DataNode 接收心跳和块报告。收到心跳表示 DataNode 正常运行。块报告包含 DataNode 上所有块的列表。

HDFS DataNodes

副本放置:第一步

副本放置对于 HDFS 的可靠性和性能至关重要。优化副本放置使 HDFS 区别于大多数其他分布式文件系统。这是一个需要大量调整和经验的功能。机架感知副本放置策略的目的是提高数据可靠性、可用性和网络带宽利用率。副本放置策略的当前实现是朝着这个方向做出的首次努力。实施此策略的短期目标是在生产系统上验证它、更多地了解其行为,并建立一个基础来测试和研究更复杂的策略。

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

NameNode 通过Hadoop 机架感知中概述的过程确定每个 DataNode 所属的机架 ID。一个简单但非最优的策略是将副本放置在唯一的机架上。当整个机架发生故障时,这可以防止数据丢失,并在读取数据时允许使用多个机架的带宽。此策略在集群中均匀分布副本,这使得在组件发生故障时很容易平衡负载。但是,此策略会增加写入成本,因为写入需要将块传输到多个机架。

对于常见情况,当复制因子为三时,HDFS 的放置策略是将一个副本放在本地机器上(如果写入器在数据节点上),否则放在与写入器位于同一机架中的随机数据节点上,将另一个副本放在不同(远程)机架中的节点上,并将最后一个副本放在同一远程机架中的不同节点上。此策略减少了机架间写入流量,这通常会提高写入性能。机架故障的可能性远小于节点故障;此策略不会影响数据可靠性和可用性保证。但是,它不会减少读取数据时使用的总网络带宽,因为一个块只被放置在两个唯一机架中,而不是三个。使用此策略,块的副本不会均匀分布在机架中。两个副本位于一个机架的不同节点上,剩余的副本位于其他机架之一的节点上。此策略提高了写入性能,同时不影响数据可靠性或读取性能。

如果复制因子大于 3,则第 4 个及后续副本的放置将随机确定,同时将每个机架的副本数保持在上限以下(基本上为 (replicas - 1) / racks + 2)。

由于 NameNode 不允许数据节点拥有同一块的多个副本,因此创建的最大副本数是当时的数据节点总数。

在 HDFS 中添加了对 存储类型和存储策略 的支持后,NameNode 除了上述机架感知之外,还会考虑策略来进行副本放置。NameNode 首先根据机架感知选择节点,然后检查候选节点是否具有与文件关联的策略所需的存储。如果候选节点没有存储类型,则 NameNode 会寻找另一个节点。如果在第一条路径中找不到足够放置副本的节点,则 NameNode 会在第二条路径中寻找具有后备存储类型的节点。

此处描述的当前默认副本放置策略是一个正在进行的工作。

副本选择

为了最大限度地减少全局带宽消耗和读取延迟,HDFS 尝试从最接近读取器的副本满足读取请求。如果在与读取器节点相同的机架上存在副本,则该副本优先满足读取请求。如果 HDFS 集群跨越多个数据中心,则位于本地数据中心的副本优先于任何远程副本。

块放置策略

如上所述,当复制因子为 3 时,HDFS 的放置策略是:如果写入器位于数据节点上,则将一个副本放在本地机器上,否则放在与写入器位于同一机架上的随机数据节点上;另一个副本放在不同(远程)机架上的节点上;最后一个副本放在同一远程机架上的不同节点上。如果复制因子大于 3,则第 4 个及后续副本的放置将随机确定,同时将每个机架上的副本数量保持在上限以下(基本上为 (replicas - 1) / racks + 2)。除此之外,HDFS 还支持 4 种不同的可插入块放置策略。用户可以根据自己的基础设施和用例选择策略。默认情况下,HDFS 支持 BlockPlacementPolicyDefault。

安全模式

在启动时,NameNode 会进入一个称为安全模式的特殊状态。当 NameNode 处于安全模式状态时,不会复制数据块。NameNode 从 DataNode 接收心跳和块报告消息。块报告包含 DataNode 托管的数据块列表。每个块都有指定的最少副本数。当该数据块的最小副本数已向 NameNode 签入时,该块被视为已安全复制。在可配置的安全复制数据块百分比向 NameNode 签入后(再加 30 秒),NameNode 将退出安全模式状态。然后,它将确定(如果有)副本数仍然少于指定数量的数据块列表。然后,NameNode 将这些块复制到其他 DataNode。

文件系统元数据的持久性

HDFS 命名空间由 NameNode 存储。NameNode 使用称为 EditLog 的事务日志来持久记录文件系统元数据发生的每个更改。例如,在 HDFS 中创建新文件会导致 NameNode 在 EditLog 中插入一条记录来指示此操作。类似地,更改文件的复制因子会导致在 EditLog 中插入一条新记录。NameNode 使用其本地主机操作系统文件系统中的一个文件来存储 EditLog。整个文件系统命名空间(包括块到文件和文件系统属性的映射)存储在称为 FsImage 的文件中。FsImage 也作为文件存储在 NameNode 的本地文件系统中。

NameNode 在内存中保留整个文件系统命名空间和文件块映射的映像。当 NameNode 启动或检查点由可配置阈值触发时,它会从磁盘读取 FsImage 和 EditLog,将 EditLog 中的所有事务应用到 FsImage 的内存中表示,并将此新版本刷新到磁盘上的新 FsImage 中。然后它可以截断旧的 EditLog,因为它的事务已应用到持久的 FsImage。此过程称为检查点。检查点的目的是通过对文件系统元数据进行快照并将其保存到 FsImage 中,确保 HDFS 对文件系统元数据具有一个一致的视图。尽管读取 FsImage 很有效,但直接对 FsImage 进行增量编辑效率不高。我们不会为每个编辑修改 FsImage,而是将编辑持久保存到 Editlog 中。在检查点期间,Editlog 中的更改将应用到 FsImage。检查点可以在给定的时间间隔(以秒为单位的 dfs.namenode.checkpoint.period)触发,或者在累积给定数量的文件系统事务后触发(dfs.namenode.checkpoint.txns)。如果设置了这两个属性,则达到第一个阈值会触发检查点。

DataNode 将 HDFS 数据存储在本地文件系统中的文件中。DataNode 不了解 HDFS 文件。它将每个 HDFS 数据块存储在本地文件系统中的一个单独文件中。DataNode 不会在同一目录中创建所有文件。相反,它使用启发式方法来确定每个目录中的最佳文件数,并适当地创建子目录。在同一目录中创建所有本地文件并不是最佳选择,因为本地文件系统可能无法有效支持单个目录中的大量文件。当 DataNode 启动时,它会扫描其本地文件系统,生成与这些本地文件中的每一个相对应的所有 HDFS 数据块的列表,并将此报告发送给 NameNode。该报告称为块报告

通信协议

所有 HDFS 通信协议都分层在 TCP/IP 协议之上。客户端在 NameNode 机器上建立到可配置 TCP 端口的连接。它与 NameNode 通信客户端协议。DataNode 使用 DataNode 协议与 NameNode 通信。远程过程调用 (RPC) 抽象封装了客户端协议和 DataNode 协议。根据设计,NameNode 绝不会发起任何 RPC。相反,它只响应 DataNode 或客户端发出的 RPC 请求。

健壮性

HDFS 的主要目标是即使在发生故障的情况下也能可靠地存储数据。三种常见的故障类型是 NameNode 故障、DataNode 故障和网络分区。

数据磁盘故障、心跳和重新复制

每个 DataNode 定期向 NameNode 发送心跳消息。网络分区可能导致部分 DataNode 失去与 NameNode 的连接。NameNode 通过心跳消息的缺失检测到此情况。NameNode 将没有最新心跳消息的 DataNode 标记为已死,并且不会向其转发任何新的 IO 请求。向已死 DataNode 注册的任何数据都不再可供 HDFS 使用。DataNode 死亡可能导致某些块的复制因子低于其指定值。NameNode 会持续跟踪哪些块需要复制,并在必要时启动复制。重新复制的必要性可能是由于多种原因:DataNode 可能变得不可用,副本可能已损坏,DataNode 上的硬盘可能发生故障,或者文件的复制因子可能增加。

标记 DataNode 为已死的超时时间非常长(默认情况下超过 10 分钟),以避免由 DataNode 状态波动引起的复制风暴。用户可以设置较短的时间间隔来标记 DataNode 为陈旧,并通过针对性能敏感的工作负载进行配置来避免在读取和/或写入时出现陈旧节点。

群集重新平衡

HDFS 架构与数据重新平衡方案兼容。如果某个 DataNode 上的可用空间低于某个阈值,方案可能会自动将数据从一个 DataNode 移动到另一个 DataNode。如果对某个特定文件突然出现高需求,方案可能会动态创建其他副本并在集群中重新平衡其他数据。这些类型的数据重新平衡方案尚未实现。

数据完整性

从 DataNode 获取的数据块可能会损坏。这种损坏可能是由于存储设备故障、网络故障或软件错误造成的。HDFS 客户端软件对 HDFS 文件的内容执行校验和检查。当客户端创建 HDFS 文件时,它会计算文件每个块的校验和,并将这些校验和存储在同一 HDFS 命名空间中的一个单独的隐藏文件中。当客户端检索文件内容时,它会验证从每个 DataNode 接收的数据是否与关联的校验和文件中存储的校验和匹配。如果不匹配,则客户端可以选择从具有该块副本的另一个 DataNode 检索该块。

元数据磁盘故障

FsImage 和 EditLog 是 HDFS 的中心数据结构。这些文件的损坏会导致 HDFS 实例无法正常运行。出于此原因,可以将 NameNode 配置为支持维护 FsImage 和 EditLog 的多个副本。对 FsImage 或 EditLog 的任何更新都会导致每个 FsImage 和 EditLog 同步更新。对 FsImage 和 EditLog 的多个副本进行这种同步更新可能会降低 NameNode 每秒支持的命名空间事务速率。但是,这种降低是可以接受的,因为即使 HDFS 应用程序本质上非常数据密集,但它们并不是元数据密集型的。当 NameNode 重新启动时,它会选择最新的 FsImage 和 EditLog 来使用。

提高对故障的恢复能力的另一种选择是使用多个 NameNode 启用高可用性,方法是使用 NFS 上的共享存储 或使用 分布式编辑日志(称为日志)。后者是推荐的方法。

快照

快照支持在特定时间点存储数据副本。快照功能的一种用法可能是将损坏的 HDFS 实例回滚到以前已知的良好时间点。

数据组织

数据块

HDFS 旨在支持非常大的文件。与 HDFS 兼容的应用程序是那些处理大型数据集的应用程序。这些应用程序只写入数据一次,但会读取一次或多次,并且要求以流式速度满足这些读取。HDFS 支持文件上的“一次写入多次读取”语义。HDFS 使用的典型块大小为 128 MB。因此,一个 HDFS 文件被切分为 128 MB 大小的块,并且如果可能,每个块都将驻留在不同的 DataNode 上。

复制管道

当客户端将数据写入复制因子为三的 HDFS 文件时,NameNode 使用复制目标选择算法检索 DataNode 列表。此列表包含将托管该块的副本的 DataNode。然后,客户端写入第一个 DataNode。第一个 DataNode 开始分批接收数据,将每个部分写入其本地存储库,并将该部分传输到列表中的第二个 DataNode。第二个 DataNode 继而开始接收数据块的每个部分,将该部分写入其存储库,然后将该部分刷新到第三个 DataNode。最后,第三个 DataNode 将数据写入其本地存储库。因此,一个 DataNode 可以从管道中的前一个 DataNode 接收数据,同时将数据转发到管道中的下一个 DataNode。因此,数据从一个 DataNode 传输到下一个 DataNode。

可访问性

可以通过多种方式从应用程序访问 HDFS。本机上,HDFS 为应用程序提供 FileSystem Java API 以供使用。此 Java API 的 C 语言包装器REST API 也可用。此外,HTTP 浏览器还可以用于浏览 HDFS 实例的文件。通过使用 NFS 网关,可以将 HDFS 挂载为客户端本地文件系统的一部分。

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

FS shell 针对需要脚本语言与存储数据进行交互的应用程序。

DFSAdmin

DFSAdmin 命令集用于管理 HDFS 集群。这些命令仅由 HDFS 管理员使用。以下是一些示例操作/命令对

操作 命令
将集群置于安全模式 bin/hdfs dfsadmin -safemode enter
生成 DataNode 列表 bin/hdfs dfsadmin -report
重新启用或停用 DataNode bin/hdfs dfsadmin -refreshNodes

浏览器界面

典型的 HDFS 安装会配置一个 Web 服务器,通过可配置的 TCP 端口公开 HDFS 命名空间。这允许用户浏览 HDFS 命名空间并使用 Web 浏览器查看其文件的内容。

空间回收

文件删除和取消删除

如果启用了回收站配置,FS Shell 删除的文件不会立即从 HDFS 中删除。相反,HDFS 会将其移动到回收站目录(每个用户在 /user/<username>/.Trash 下都有自己的回收站目录)。只要文件保留在回收站中,就可以快速恢复。

最近删除的文件会被移动到当前回收站目录(/user/<username>/.Trash/Current),并且在可配置的时间间隔内,HDFS 会为当前回收站目录中的文件创建检查点(在 /user/<username>/.Trash/<date> 下),并在过期时删除旧检查点。有关回收站的检查点,请参阅FS shell 的 expunge 命令

在回收站中达到其生命期后,NameNode 会从 HDFS 命名空间中删除该文件。删除文件会导致与该文件关联的块被释放。请注意,用户删除文件的时间与 HDFS 中可用空间相应增加的时间之间可能存在明显的延迟。

以下是 FS Shell 从 HDFS 中删除文件的示例。我们在 delete 目录下创建了 2 个文件 (test1 和 test2)

$ hadoop fs -mkdir -p delete/test1
$ hadoop fs -mkdir -p delete/test2
$ hadoop fs -ls delete/
Found 2 items
drwxr-xr-x   - hadoop hadoop          0 2015-05-08 12:39 delete/test1
drwxr-xr-x   - hadoop hadoop          0 2015-05-08 12:40 delete/test2

我们将删除文件 test1。下面的注释显示该文件已移动到回收站目录。

$ hadoop fs -rm -r delete/test1
Moved: hdfs://localhost:8020/user/hadoop/delete/test1 to trash at: hdfs://localhost:8020/user/hadoop/.Trash/Current

现在,我们将使用 skipTrash 选项删除该文件,该选项不会将文件发送到回收站。它将从 HDFS 中完全删除。

$ hadoop fs -rm -r -skipTrash delete/test2
Deleted delete/test2

现在我们可以看到,回收站目录仅包含文件 test1。

$ hadoop fs -ls .Trash/Current/user/hadoop/delete/
Found 1 items\
drwxr-xr-x   - hadoop hadoop          0 2015-05-08 12:39 .Trash/Current/user/hadoop/delete/test1

因此,文件 test1 进入回收站,文件 test2 被永久删除。

降低复制因子

当文件的复制因子减少时,NameNode 会选择可以删除的多余副本。下一个心跳将此信息传输到 DataNode。然后,DataNode 会删除相应的块,并且相应的可用空间会出现在集群中。同样,在 setReplication API 调用完成和集群中出现可用空间之间可能存在时间延迟。

参考

Hadoop JavaDoc API

HDFS 源代码:https://hadoop.apache.org/version_control.html