Hadoop 分布式文件系统 (HDFS) 旨在可靠地存储非常大的数据集,并以高带宽将这些数据集流式传输到用户应用程序。在一个大型集群中,数千台服务器直接托管连接的存储并执行用户应用程序任务。通过在许多服务器上分布存储和计算,资源可以随着需求的增长而增长,同时在每个规模上保持经济性。我们描述了 HDFS 的架构,并报告了使用 HDFS 在 Yahoo! 管理 40 PB 企业数据的经验。
Hadoop1 提供了一个分布式文件系统和一个框架,用于使用 MapReduce [DG04] 范例分析和转换非常大的数据集。虽然 HDFS 的接口是根据 Unix 文件系统设计的,但为了提高当前应用程序的性能,牺牲了对标准的忠实度。
Hadoop 的一个重要特征是将数据和计算划分为许多(数千个)主机,并在靠近其数据的并行环境中执行应用程序计算。Hadoop 集群通过简单地添加商品服务器来扩展计算能力、存储能力和 I/O 带宽。Yahoo! 的 Hadoop 集群跨越 40,000 台服务器,存储 40 PB 的应用程序数据,其中最大的集群有 4000 台服务器。全球有 100 多个其他组织报告使用 Hadoop。
HDFS 将文件系统元数据和应用程序数据分别存储。与其他分布式文件系统(如 PVFS [CIRT00]、Lustre2 和 GFS [GGL03])一样,HDFS 将元数据存储在一个专用服务器上,称为 NameNode。应用程序数据存储在称为 DataNode 的其他服务器上。所有服务器都完全连接,并使用基于 TCP 的协议相互通信。与 Lustre 和 PVFS 不同,HDFS 中的 DataNode 不依赖于 RAID 等数据保护机制来使数据持久化。相反,与 GFS 一样,文件内容在多个 DataNode 上复制以确保可靠性。在确保数据持久性的同时,这种策略还具有以下额外优势:数据传输带宽倍增,并且有更多机会将计算定位在所需数据附近。
HDFS 命名空间是文件和目录的层次结构。文件和目录在 NameNode 上由 inode 表示。inode 记录属性,如权限、修改和访问时间、命名空间和磁盘空间配额。文件内容被分成大型块(通常为 128 MB,但用户可以在每个文件的基础上选择),文件的每个块都在多个 DataNode 上独立复制(通常为三个,但用户可以在每个文件的基础上选择)。NameNode维护命名空间树以及块到 DataNode 的映射。当前设计每个集群只有一个 NameNode。集群可以有数千个 DataNode 和每个集群数万个 HDFS 客户端,因为每个 DataNode 可能会同时执行多个应用程序任务。
定义名称系统元数据的 inode 和块列表称为镜像。NameNode 将整个命名空间镜像保存在 RAM 中。NameNode 本地原生文件系统中存储的镜像的持久记录称为检查点。NameNode 在其本地原生文件系统中将对 HDFS 的更改记录在预写日志中,称为日志。块副本的位置不属于持久检查点。
每个客户端发起的交易都会记录在日志中,并且在向客户端发送确认之前,日志文件会被刷新并同步。NameNode 从不更改检查点文件;在重新启动时、管理员请求时或下一节中描述的 CheckpointNode 请求时,会写入一个新文件。在启动期间,NameNode 从检查点初始化命名空间镜像,然后从日志中重放更改。在 NameNode 开始为客户端提供服务之前,会将新的检查点和空的日志写回存储目录。
为了提高持久性,检查点和日志的冗余副本通常存储在多个独立的本地卷和远程 NFS 服务器上。第一个选择可以防止单个卷故障导致数据丢失,第二个选择可以防止整个节点故障导致数据丢失。如果 NameNode 遇到写入日志到其中一个存储目录的错误,它会自动将该目录从存储目录列表中排除。如果不可用任何存储目录,NameNode 会自动关闭自己。
NameNode 是一个多线程系统,可以同时处理来自多个客户端的请求。将交易保存到磁盘会成为瓶颈,因为所有其他线程都需要等到其中一个线程发起的同步刷新和同步过程完成。为了优化此过程,NameNode 会将多个交易批处理。当 NameNode 的某个线程启动刷新和同步操作时,此时批处理的所有交易都会一起提交。剩余的线程只需要检查他们的交易是否已保存,而不需要启动刷新和同步操作。
DataNode 上的每个块副本在本地原生文件系统中由两个文件表示。第一个文件包含数据本身,第二个文件记录块的元数据,包括数据的校验和和生成戳。数据文件的尺寸等于块的实际长度,并且不需要额外的空间来将其向上舍入到传统的块大小,如传统文件系统中那样。因此,如果块只填充了一半,它只需要本地驱动器上完整块的一半空间。
在启动期间,每个 DataNode 会连接到 NameNode 并执行握手。握手的目的是验证 DataNode 的命名空间 ID 和软件版本。如果两者都不匹配 NameNode 的版本,DataNode 会自动关闭。
命名空间 ID 在格式化文件系统实例时分配。命名空间 ID 在集群的所有节点上持久存储。具有不同命名空间 ID 的节点将无法加入集群,从而保护文件系统的完整性。新初始化且没有命名空间 ID 的 DataNode 被允许加入集群并接收集群的命名空间 ID。
握手完成后,DataNode 会向 NameNode 注册。DataNode 持久存储其唯一的存储 ID。存储 ID 是 DataNode 的内部标识符,即使 DataNode 使用不同的 IP 地址或端口重新启动,也可以识别它。存储 ID 在 DataNode 第一次向 NameNode 注册时分配,之后不再更改。
DataNode 通过发送块报告来向 NameNode 识别其拥有的块副本。块报告包含每个块副本的块 ID、生成戳和长度,这些块副本由服务器托管。第一个块报告在 DataNode 注册后立即发送。随后的块报告每小时发送一次,并为 NameNode 提供关于块副本在集群中的位置的最新视图。
在正常操作期间,DataNode 会向 NameNode 发送心跳以确认 DataNode 正在运行,并且它托管的块副本可用。默认心跳间隔为三秒。如果 NameNode 在十分钟内没有收到来自 DataNode 的心跳,NameNode 会认为 DataNode 已停止服务,并且该 DataNode 托管的块副本不可用。然后,NameNode 会安排在其他 DataNode 上创建这些块的新副本。
来自 DataNode 的心跳还会携带有关总存储容量、存储使用率和当前正在进行的数据传输数量的信息。这些统计信息用于 NameNode 的块分配和负载均衡决策。
NameNode 不会直接向 DataNode 发送请求。它使用对心跳的回复来向 DataNode 发送指令。这些指令包括复制块到其他节点、删除本地块副本、重新注册和发送立即块报告以及关闭节点的命令。
这些命令对于维护整个系统完整性非常重要,因此即使在大型集群中,也要保持心跳频繁。NameNode 每秒可以处理数千个心跳,而不会影响其他 NameNode 操作。
用户应用程序使用 HDFS 客户端访问文件系统,HDFS 客户端是一个导出 HDFS 文件系统接口的库。
与大多数传统文件系统一样,HDFS 支持读取、写入和删除文件,以及创建和删除目录的操作。用户通过命名空间中的路径引用文件和目录。用户应用程序不需要知道文件系统元数据和存储位于不同的服务器上,或者块有多个副本。
当应用程序读取文件时,HDFS 客户端首先向 NameNode 请求托管文件块副本的 DataNode 列表。该列表按客户端的网络拓扑距离排序。客户端直接联系 DataNode 并请求传输所需的块。当客户端写入时,它首先请求 NameNode 选择 DataNode 来托管文件的第一个块的副本。客户端从节点到节点组织一个管道并发送数据。当第一个块填满时,客户端会请求选择新的 DataNode 来托管下一个块的副本。会组织一个新的管道,客户端会发送文件的后续字节。每个块的 DataNode 选择可能会有所不同。客户端、NameNode 和 DataNode 之间的交互在 图 8.1 中进行了说明。
图 8.1:HDFS 客户端创建一个新文件
与传统文件系统不同,HDFS 提供了一个 API,该 API 公开了文件块的位置。这允许像 MapReduce 框架这样的应用程序将任务调度到数据所在的位置,从而提高读取性能。它还允许应用程序设置文件的副本因子。默认情况下,文件的副本因子为三个。对于关键文件或经常访问的文件,使用更高的副本因子可以提高对故障的容忍度并提高读取带宽。
除了其主要作用为客户端请求提供服务外,HDFS 中的 NameNode 还可以在两种其他角色中执行任一角色,即 CheckpointNode 或 BackupNode。角色在节点启动时指定。
CheckpointNode 定期组合现有的检查点和日志以创建新的检查点和空的日志。CheckpointNode 通常在与 NameNode 不同的主机上运行,因为它与 NameNode 具有相同的内存要求。它从 NameNode 下载当前的检查点和日志文件,在本地合并它们,并将新的检查点返回给 NameNode。
创建定期检查点是保护文件系统元数据的其中一种方法。如果命名空间镜像或日志的所有其他持久副本都不可用,系统可以从最新的检查点启动。创建检查点还允许 NameNode 在将新的检查点上传到 NameNode 时截断日志。HDFS 集群在长时间内运行,在没有重启的情况下,日志会不断增长。如果日志变得非常大,日志文件丢失或损坏的可能性会增加。此外,非常大的日志会延长重新启动 NameNode 所需的时间。对于大型集群,处理一周的日志需要一个小时。最佳做法是每天创建一个检查点。
HDFS 的一项新功能是 BackupNode。与 CheckpointNode 相似,BackupNode 能够创建周期性检查点,但它还维护着文件系统命名空间的内存中实时映像,该映像始终与 NameNode 的状态同步。
BackupNode 从活动 NameNode 接收命名空间事务的日志流,将其保存在自己的存储目录中的日志中,并将这些事务应用到它自己的内存中命名空间映像。NameNode 将 BackupNode 视为日志存储,与它在存储目录中处理日志文件的方式相同。如果 NameNode 发生故障,BackupNode 的内存映像和磁盘上的检查点将记录最新的命名空间状态。
BackupNode 可以创建检查点,而无需从活动 NameNode 下载检查点和日志文件,因为它已经在其内存中拥有最新的命名空间映像。这使得 BackupNode 上的检查点过程更加高效,因为它只需要将命名空间保存到其本地存储目录中。
BackupNode 可以被视为只读 NameNode。它包含所有文件系统元数据信息,除了块位置。它可以执行常规 NameNode 的所有操作,这些操作不涉及命名空间的修改或块位置的知识。使用 BackupNode 提供了在没有持久性存储的情况下运行 NameNode 的选项,将持久化命名空间状态的责任委托给 BackupNode。
在软件升级过程中,由于软件错误或人为错误而导致文件系统损坏的可能性会增加。在 HDFS 中创建快照的目的是在升级期间最大程度地减少对系统中存储数据的潜在损害。
快照机制允许管理员持久地保存文件系统的当前状态,以便如果升级导致数据丢失或损坏,可以回滚升级并将 HDFS 恢复到快照时的命名空间和存储状态。
快照(只能存在一个)是在系统启动时由集群管理员选择创建的。如果请求快照,NameNode 首先读取检查点和日志文件,并将它们合并到内存中。然后它将新的检查点和空日志写入新位置,以便旧的检查点和日志保持不变。
在握手期间,NameNode 指示 DataNode 是否创建本地快照。DataNode 上的本地快照不能通过复制包含数据文件的目录来创建,因为这将需要将集群中每个 DataNode 的存储容量加倍。相反,每个 DataNode 都创建存储目录的副本,并将现有块文件硬链接到其中。当 DataNode 删除块时,它只删除硬链接,而追加期间的块修改使用写时复制技术。因此,旧的块副本保持不变,位于旧目录中。
集群管理员可以选择在重启系统时将 HDFS 回滚到快照状态。NameNode 恢复创建快照时保存的检查点。DataNode 恢复以前重命名的目录,并启动后台进程以删除快照创建后创建的块副本。选择回滚后,没有回滚到未来的规定。集群管理员可以通过命令系统放弃快照来恢复快照占用的存储空间;对于在升级期间创建的快照,这将完成软件升级。
系统演变可能会导致 NameNode 的检查点和日志文件格式发生变化,或者 DataNode 上块副本文件的表示数据发生变化。布局版本标识数据表示格式,并持久地存储在 NameNode 和 DataNode 的存储目录中。在启动期间,每个节点将当前软件的布局版本与其存储目录中存储的版本进行比较,并自动将旧格式的数据转换为新格式。转换需要在系统使用新的软件布局版本重新启动时强制创建快照。
当然,文件系统的全部意义在于将数据存储在文件中。要了解 HDFS 是如何做到这一点的,我们必须了解读写是如何工作的,以及块是如何管理的。
应用程序通过创建新文件并将数据写入其中来向 HDFS 添加数据。在文件关闭后,写入的字节不能被更改或删除,除非通过重新打开文件进行追加来添加新数据。HDFS 实现单写多读模型。
打开文件以进行写入的 HDFS 客户端被授予该文件的租约;其他客户端无法写入该文件。写入客户端通过向 NameNode 发送心跳来定期续租。当文件关闭时,租约将被撤销。租约期限受软限制和硬限制约束。在软限制到期之前,编写者可以确定对文件的独占访问权限。如果软限制到期,并且客户端未能关闭文件或续租,则其他客户端可以抢占租约。如果在硬限制到期(一小时)后,客户端未能续租,HDFS 假设客户端已退出,并将自动代表编写者关闭文件,并恢复租约。编写者的租约不会阻止其他客户端读取文件;一个文件可能有多个并发读者。
HDFS 文件由块组成。当需要新块时,NameNode 将分配一个具有唯一块 ID 的块,并确定一个 DataNode 列表来托管该块的副本。DataNode 形成一个管道,其顺序最大程度地减少了从客户端到最后一个 DataNode 的总网络距离。字节以数据包序列的形式推送到管道。应用程序写入的第一个字节缓冲在客户端侧。数据包缓冲区填满后(通常为 64 KB),数据将推送到管道。下一个数据包可以在收到前一个数据包的确认之前推送到管道。未完成的数据包数量受客户端未完成的数据包窗口大小限制。
数据写入 HDFS 文件后,HDFS 不保证数据对新读取器可见,直到文件关闭。如果用户应用程序需要可见性保证,则可以显式调用 hflush 操作。然后,当前数据包将立即推送到管道,hflush 操作将等待,直到管道中的所有 DataNode 确认数据包的成功传输。然后,所有在 hflush 操作之前写入的数据都将对读者可见。
图 8.2:写入块时的数据管道
如果未发生错误,块构建将经历三个阶段,如图 图 8.2 所示,该图说明了一个包含三个 DataNode (DN) 和五个数据包的块的管道。在图中,粗线表示数据包,虚线表示确认消息,细线表示用于设置和关闭管道的控制消息。垂直线表示客户端和三个 DataNode 上的活动,时间从上到下进行。从 t0
到 t1
是管道设置阶段。时间间隔 t1
到 t2
是数据流阶段,其中 t1
是第一个数据包发送的时间,t2
是最后一个数据包的确认收到时间。这里,hflush 操作传输 packet 2
。hflush 指示与数据包数据一起传输,而不是单独的操作。最后的时间间隔 t2
到 t3
是此块的管道关闭阶段。
在一个包含数千个节点的集群中,节点故障(最常见的是存储故障)是每天都会发生的。存储在 DataNode 上的副本可能会由于内存、磁盘或网络故障而损坏。HDFS 为 HDFS 文件的每个数据块生成并存储校验和。HDFS 客户端在读取时验证校验和,以帮助检测由客户端、DataNode 或网络引起的任何损坏。当客户端创建 HDFS 文件时,它会计算每个块的校验和序列,并将校验和序列与数据一起发送到 DataNode。DataNode 将校验和存储在与块数据文件分开的元数据文件中。当 HDFS 读取文件时,每个块的数据和校验和都会发送到客户端。客户端计算接收数据的校验和,并验证新计算的校验和是否与接收到的校验和匹配。如果不匹配,客户端会通知 NameNode 损坏的副本,然后从另一个 DataNode 获取该块的不同副本。
当客户端打开文件进行读取时,它会从 NameNode 获取块列表和每个块副本的位置。每个块的位置按它们到读取器的距离排序。读取块的内容时,客户端首先尝试最接近的副本。如果读取尝试失败,客户端会尝试序列中的下一个副本。读取可能失败,因为目标 DataNode 不可用、节点不再托管该块的副本,或者在测试校验和时发现副本已损坏。
HDFS 允许客户端读取打开以进行写入的文件。读取打开以进行写入的文件时,NameNode 不知道正在写入的最后一个块的长度。在这种情况下,客户端会询问其中一个副本以获取最新长度,然后再开始读取其内容。
HDFS I/O 的设计特别针对批处理系统(如 MapReduce)进行了优化,这些系统需要高吞吐量来进行顺序读写。正在进行的工作将改善对需要实时数据流或随机访问的应用程序的读写响应时间。
对于一个大型集群,在扁平拓扑中连接所有节点可能不切实际。一种常见的做法是将节点分散到多个机架上。一个机架的节点共享一个交换机,机架交换机通过一个或多个核心交换机连接。不同机架上的两个节点之间的通信必须经过多个交换机。在大多数情况下,同一机架内节点之间的网络带宽大于不同机架内节点之间的网络带宽。图 8.3 描述了一个包含两个机架的集群,每个机架包含三个节点。
图 8.3:集群拓扑
HDFS 通过两个节点之间的距离来估计网络带宽。从一个节点到其父节点的距离假定为 1。两个节点之间的距离可以通过将其到最近公共祖先的距离相加来计算。两个节点之间的距离越短,它们可以用来传输数据的带宽就越大。
HDFS 允许管理员配置一个脚本,该脚本根据节点地址返回节点的机架标识。NameNode 是解析每个 DataNode 的机架位置的中心位置。当 DataNode 向 NameNode 注册时,NameNode 会运行配置的脚本以决定节点属于哪个机架。如果没有配置这样的脚本,NameNode 会假设所有节点都属于默认的单个机架。
副本的放置对于 HDFS 数据可靠性和读写性能至关重要。一个好的副本放置策略应该提高数据可靠性、可用性和网络带宽利用率。目前,HDFS 提供了一个可配置的块放置策略接口,以便用户和研究人员可以试验和测试适合其应用程序的备选策略。
默认的 HDFS 块放置策略在最小化写入成本和最大化数据可靠性、可用性和聚合读取带宽之间取得了平衡。当创建新块时,HDFS 将第一个副本放置在写入器所在的节点上。第二个和第三个副本放置在不同机架上的两个不同节点上。其余的副本放置在随机节点上,限制条件是每个节点上最多放置一个副本,并且如果可能,在同一个机架上最多放置两个副本。将第二个和第三个副本放置在不同机架上的选择可以更好地将单个文件的块副本分散在整个集群中。如果前两个副本放置在同一个机架上,那么对于任何文件,三分之二的块副本都将位于同一个机架上。
在选择所有目标节点后,将节点按其与第一个副本的距离顺序组织成管道。数据按此顺序推送到节点。对于读取,NameNode 首先检查客户端的主机是否位于集群中。如果是,则按其与读取器的距离顺序将块位置返回给客户端。按此优先顺序从 DataNode 读取块。
此策略减少了机架间和节点间写入流量,并总体上提高了写入性能。由于机架故障的可能性远小于节点故障的可能性,因此此策略不会影响数据可靠性和可用性保证。在通常的三个副本情况下,它可以减少读取数据时使用的聚合网络带宽,因为一个块仅放置在两个唯一的机架中,而不是三个。
NameNode 努力确保每个块始终具有预期的副本数量。当从 DataNode 收到块报告时,NameNode 会检测到块已变得复制不足或复制过度。当块变得复制过度时,NameNode 会选择一个副本进行删除。NameNode 会优先不减少托管副本的机架数量,其次会优先从可用磁盘空间最少的 DataNode 删除副本。目标是在不减少块可用性的情况下平衡 DataNode 之间的存储利用率。
当块变得复制不足时,它将被放入复制优先级队列。只有单个副本的块具有最高优先级,而副本数量大于其复制因子三分之二的块具有最低优先级。一个后台线程定期扫描复制队列的头部,以决定将新的副本放置在何处。块复制遵循与新块放置类似的策略。如果现有副本的数量为 1,则 HDFS 将下一个副本放置在不同的机架上。如果块有两个现有的副本,如果两个现有的副本位于同一个机架上,则第三个副本将放置在不同的机架上;否则,第三个副本将放置在与现有副本相同的机架上的不同节点上。这里的目标是降低创建新副本的成本。
NameNode 还确保块的所有副本都不位于一个机架上。如果 NameNode 检测到块的副本最终位于一个机架上,则 NameNode 会将该块视为复制错误,并使用上述相同的块放置策略将该块复制到不同的机架。在 NameNode 收到副本已创建的通知后,该块将变得复制过度。然后,NameNode 将决定删除旧副本,因为过度复制策略优先不减少机架数量。
HDFS 块放置策略没有考虑 DataNode 磁盘空间利用率。这是为了避免将新的(更有可能被引用)数据放置在具有大量可用存储空间的 DataNode 的一小部分子集中。因此,数据可能并不总是均匀地放置在所有 DataNode 上。当新节点添加到集群时,也会出现不平衡现象。
平衡器是一种平衡 HDFS 集群上的磁盘空间使用情况的工具。它将阈值作为输入参数,这是一个介于 0 和 1 之间的分数。如果每个 DataNode 的节点3利用率与整个集群4的利用率之间的差异不超过阈值,则集群处于平衡状态。
该工具被部署为一个应用程序,可以由集群管理员运行。它迭代地将副本从利用率较高的 DataNode 移动到利用率较低的 DataNode。平衡器的一个关键要求是维护数据可用性。在选择要移动的副本以及确定其目标时,平衡器保证该决策不会减少副本数量或机架数量。
平衡器通过最小化机架间数据复制来优化平衡过程。如果平衡器决定需要将副本 A 移动到不同的机架,并且目标机架恰好拥有同一个块的副本 B,则数据将从副本 B 而不是副本 A 复制。
一个配置参数限制了重新平衡操作消耗的带宽。允许的带宽越高,集群越快能够达到平衡状态,但与应用程序进程的竞争也越大。
每个 DataNode 都运行一个块扫描器,定期扫描其块副本并验证存储的校验和是否与块数据匹配。在每个扫描周期中,块扫描器会调整读取带宽,以便在可配置的时间段内完成验证。如果客户端读取完整的块并且校验和验证成功,则会通知 DataNode。DataNode 将其视为对副本的验证。
每个块的验证时间存储在人类可读的日志文件中。在任何时候,顶层 DataNode 目录中最多有两个文件,当前日志和先前日志。新的验证时间将追加到当前文件中。相应地,每个 DataNode 都有一个按副本验证时间排序的内存扫描列表。
每当读取客户端或块扫描器检测到损坏的块时,它都会通知 NameNode。NameNode 会将该副本标记为损坏,但不会立即安排删除该副本。相反,它会开始复制该块的良好副本。只有当良好副本的数量达到块的复制因子时,才会安排删除损坏的副本。此策略旨在尽可能长时间地保存数据。因此,即使块的所有副本都已损坏,该策略也允许用户从损坏的副本中检索其数据。
集群管理员会指定要停用的节点列表。一旦一个 DataNode 被标记为停用,它将不会被选为副本放置的目标,但它将继续为读取请求提供服务。NameNode 开始安排将其块复制到其他 DataNode。一旦 NameNode 检测到停用 DataNode 上的所有块都已复制,该节点将进入停用状态。然后,它可以安全地从集群中删除,而不会危及任何数据可用性。
在处理大型数据集时,将数据复制进出 HDFS 集群是一项艰巨的任务。HDFS 提供了一个名为 DistCp 的工具,用于进行大型集群间/集群内并行复制。它是一个 MapReduce 作业;每个映射任务将源数据的一部分复制到目标文件系统中。MapReduce 框架会自动处理并行任务调度、错误检测和恢复。
雅虎的大型 HDFS 集群包括大约 4000 个节点。典型的集群节点具有两个以 2.5 GHz 运行的四核 Xeon 处理器,4-12 个直接连接的 SATA 磁盘(每个磁盘容量为 2 TB),24 GB 内存和 1 千兆以太网连接。磁盘空间的 70% 分配给 HDFS。剩余部分保留给操作系统(Red Hat Linux)、日志和映射任务输出的溢出空间(MapReduce 中间数据不会存储在 HDFS 中)。
单个机架中的 40 个节点共享一个 IP 交换机。机架交换机连接到八个核心交换机中的每一个。核心交换机提供机架之间以及与集群外资源之间的连接。对于每个集群,NameNode 和 BackupNode 主机都专门配置了高达 64 GB 的内存;应用程序任务永远不会分配给这些主机。总的来说,一个拥有 4000 个节点的集群拥有 11 PB(PB,拍字节;1000 TB)的存储空间,这些存储空间以块的形式提供,并被复制三次,从而为用户应用程序提供了 3.7 PB 的净存储空间。多年来,HDFS 一直在使用中,被选为集群节点的主机已经从改进的技术中获益。新的集群节点始终拥有更快的处理器、更大的磁盘和更大的内存。速度较慢、规模较小的节点被淘汰或被分配到为 Hadoop 的开发和测试保留的集群中。
在一个大型示例集群(4000 个节点)中,大约有 6500 万个文件和 8000 万个块。由于每个块通常被复制三次,因此每个数据节点都托管 60000 个块副本。每天,用户应用程序会在集群中创建 200 万个新文件。雅虎的 Hadoop 集群中的 40000 个节点提供了 40 PB 的在线数据存储空间。
成为雅虎技术套件的关键组成部分意味着解决技术问题,这些问题是成为一个研究项目和成为许多 PB 公司数据的保管者之间的区别。最重要的是数据的健壮性和持久性。但也同样重要的是经济的性能、为用户社区成员提供的资源共享的规定以及系统运营商的易于管理。
将数据复制三次是防止由于不相关节点故障而导致数据丢失的可靠保障。雅虎可能从未以这种方式丢失过一个块;对于一个大型集群,一年内丢失一个块的概率小于 0.005。关键的理解是,每月大约有 0.8% 的节点发生故障。(即使该节点最终恢复,也不会采取任何措施来恢复它可能托管的数据。)因此,对于上面描述的样本大型集群,每天都会丢失一到两个节点。同一个集群将在大约两分钟内重新创建失败节点上托管的 60000 个块副本:重新复制速度很快,因为它是一个与集群规模成正比的并行问题。几个节点在两分钟内同时发生故障,导致某个块的所有副本丢失的概率确实很小。
节点的相关故障是另一种威胁。在这方面最常见的故障是机架或核心交换机的故障。HDFS 可以承受丢失一个机架交换机(每个块在其他机架上都有一份副本)。一些核心交换机故障会有效地将集群的一部分与多个机架断开连接,在这种情况下,一些块可能会变得不可用。无论哪种情况,修复交换机都会将不可用的副本恢复到集群。另一种相关故障是集群意外或故意断电。如果断电范围跨越多个机架,那么一些块可能会变得不可用。但是,恢复供电可能不是补救措施,因为 0.5% 到 1% 的节点无法在完全通电重启后幸存下来。从统计学上讲,实际上,一个大型集群会在通电重启期间丢失少量块。
除了节点的完全故障之外,存储的数据还可能被损坏或丢失。块扫描器每两周扫描大型集群中的所有块,在此过程中会找到大约 20 个坏副本。坏副本在被发现时会被替换。
随着 HDFS 的使用不断增长,文件系统本身必须引入方法在大量不同的用户之间共享资源。第一个这样的功能是权限框架,该框架紧密模仿了 Unix 文件和目录的权限方案。在这个框架中,文件和目录对所有者、与文件或目录关联的用户组的其他成员以及所有其他用户具有单独的访问权限。Unix(POSIX)和 HDFS 之间的主要区别在于,HDFS 中的普通文件既没有执行权限也没有粘滞位。
在早期版本的 HDFS 中,用户身份很弱:你是你的主机所说的那个人。在访问 HDFS 时,应用程序客户端只需查询本地操作系统以获取用户身份和组成员资格。在新框架中,应用程序客户端必须向名称系统提供从可信来源获得的凭据。不同的凭据管理是可能的;最初的实现使用 Kerberos。用户应用程序可以使用相同的框架来确认名称系统也具有可信的身份。名称系统还可以要求参与群集的每个数据节点提供凭据。
可用于数据存储的总空间由数据节点的数量和为每个节点配置的存储空间决定。早期使用 HDFS 的经验表明,需要一些方法来在用户社区之间执行资源分配策略。不仅必须强制执行共享的公平性,而且当用户应用程序可能涉及数千个主机写入数据时,防止应用程序意外耗尽资源也很重要。对于 HDFS,由于系统元数据始终驻留在 RAM 中,因此命名空间的大小(文件和目录的数量)也是有限的资源。为了管理存储和命名空间资源,每个目录都可以分配一个配额,用于该目录开始的命名空间子树中文件的总占用空间。也可以为子树中的文件和目录的总数设置单独的配额。
虽然 HDFS 的架构假定大多数应用程序将流式传输大型数据集作为输入,但 MapReduce 编程框架可能倾向于生成许多小型输出文件(每个减少任务一个),从而进一步加剧命名空间资源的压力。为了方便起见,目录子树可以折叠成单个 Hadoop 存档文件。HAR 文件类似于常见的 tar、JAR 或 Zip 文件,但文件系统操作可以处理存档中的各个文件,并且 HAR 文件可以透明地用作 MapReduce 作业的输入。
NameNode 的可扩展性一直是一个关键的难题 [Shv10]。由于 NameNode 将所有命名空间和块位置保存在内存中,因此 NameNode 堆的大小限制了文件的数量,也限制了可寻址的块的数量。这也限制了 NameNode 可以支持的总集群存储。鼓励用户创建更大的文件,但这并没有发生,因为它需要更改应用程序行为。此外,我们正在看到用于 HDFS 的新类别的应用程序,这些应用程序需要存储大量的小文件。添加了配额来管理使用情况,并提供了一个存档工具,但这些并不能从根本上解决可扩展性问题。
一项新功能允许多个独立的命名空间(和 NameNode)共享集群内的物理存储。命名空间使用分组在块池下的块。块池类似于 SAN 存储系统中的逻辑单元 (LUN),而具有块池的命名空间类似于文件系统卷。
除了可扩展性之外,这种方法还提供了一些优势:它可以隔离不同应用程序的命名空间,从而提高集群的整体可用性。块池抽象允许其他服务使用块存储,可能具有不同的命名空间结构。我们计划探索其他扩展方法,例如仅在内存中存储部分命名空间,以及 NameNode 的真正分布式实现。
应用程序希望继续使用单个命名空间。可以挂载命名空间以创建这样的统一视图。与服务器端挂载表相比,客户端挂载表提供了一种高效的方法:它避免了对中央挂载表的 RPC,并且对它的故障也有容错能力。最简单的方法是拥有共享的集群范围的命名空间;这可以通过为集群的每个客户端提供相同的客户端挂载表来实现。客户端挂载表还允许应用程序创建私有命名空间视图。这类似于用于处理分布式系统中远程执行的每个进程命名空间 [PPT+93,Rad94,RP93]。
一个非常小的团队能够构建 Hadoop 文件系统,并使其足够稳定和健壮,可以在生产中使用。成功的很大一部分归功于非常简单的架构:复制的块、定期块报告和中央元数据服务器。避免完整的 POSIX 语义也有所帮助。尽管将所有元数据保存在内存中限制了命名空间的可扩展性,但这使得 NameNode 非常简单:它避免了典型文件系统的复杂锁定。Hadoop 成功的另一个原因是迅速在雅虎!使用该系统进行生产,因为它得到了快速和增量改进。文件系统非常健壮,NameNode 很少发生故障;实际上,大多数停机时间是由于软件升级造成的。直到最近才出现了故障转移解决方案(尽管是手动的)
许多人对在构建可扩展文件系统时选择 Java 感到惊讶。虽然 Java 由于其对象内存开销和垃圾回收而给 NameNode 的扩展带来了挑战,但 Java 对系统的健壮性负有责任;它避免了由于指针或内存管理错误而导致的损坏。
感谢雅虎!对 Hadoop 的投资并继续将其作为开源软件提供;80% 的 HDFS 和 MapReduce 代码是在雅虎!开发的。感谢所有 Hadoop 贡献者和合作者的宝贵贡献。
https://hadoop.apache.ac.cn
http://www.lustre.org