HDFS是hadoop的分布式文件系统的实现。它的设计目标是存储海量数据,并为分布在网络中的大量客户端提供数据访问。要想成功使用HDFS,首先必须了解其实现方式和工作原理。
HDFS架构
HDFS的设计思想基于Google文件系统(Google File System,GFS)。它的实现解决了存在于众多分布式文件系统(例如网络文件系统(NFS))中的大量问题。具体来说,HDFS的实现解决了以下问题:
能够保存非常大的数据量(TB级或PB级),HDFS的设计支持将数据散布在大量机器上,而且与其他分布式文件系统(例如NFS)相比,它支持更大的文件尺寸。
可靠地存储数据,为应对集群中单台机器的故障或不可访问,HDFS使用数据复制的方法。
为更好地与Hadoop的MapReduce集成,HDFS允许数据在本地读取并处理(数据本地性问题会在第4章中详细讨论)。
HDFS为获得可扩展性和高性能而进行的的设计也是有代价的。HDFS只适用于特定类型的应用——它不是通用的分布式文件系统。大量额外的决策和取舍主导了HDFS的架构和实现,它们包括以下方面:
HDFS针对高速流式读取性能做了优化,随之而来的代价是降低了随机查找性能。这意味着,如果应用程序需要从HDFS读取数据,那么应该避免查找,或者至少最小化查找的次数。顺序读取是访问HDFS文件的首选方式。
HDFS仅支持一组有限的文件操作——写入、删除、追加和读取,不支持更新。它假定数据一次性写入HDFS,然后多次读取。
HDFS不提供本地数据缓存机制。缓存的开销非常大,以至于应该单纯地从源文件重新读取数据,而这对于通常顺序读取大数据文件的应用程序来说并不是个问题。
HDFS被实现为一种块结构的文件系统。如图2-1所示,单个文件被拆分成固定大小的块,而这些块保存在Hadoop集群上。一个文件可以由多个块组成,这些块存储在不同的DataNode(集群中的单独一台机器)上;对于每个块来说,保存在哪个DataNode上是随机选取的。其结果是,访问某个文件通常需要访问多个DataNode,这意味着HDFS支持的文件大小远远超过单机的磁盘容量。
DataNode在其本地文件系统上以单独文件的形式保存各个HDFS数据块,但它并不了解HDFS文件自身的信息。为了进一步提高吞吐量,DataNode不会将所有文件创建在相同的文件夹中。相反,它使用启发式算法来确定每个目录下最优的文件数目,并适当地创建子目录。
对这种块结构文件系统的要求之一是能够可靠地存储、管理和访问文件元数据(关于文件和块的信息),并提供对元数据存储的快速访问。与HDFS文件自身(一次写入、多次读取的访问模型)不同,元数据结构可以被大量客户端同时修改。始终保持这些信息的同步很重要。HDFS通过引入称为NameNode的专用特殊机器来解决该问题,NameNode上保存了整个集群文件系统的所有元数据。这意味着HDFS实现了一种主/从架构。单独的NameNode(即主服务器)管理文件系统名称空间并规范客户端对文件的访问。集群中单独主控节点的存在极大地简化了系统架构。NameNode充当了单独仲裁者和全部 HDFS元数据存储库的角色。
由于每个文件元数据的量相对较小(仅包含文件名、访问权限和各个块的位置),因此NameNode将所有元数据保存在内存中,从而保证快速的随机访问。元数据存储在设计上很紧凑。其结果是,拥有4GB内存的NameNode能够支持巨大数目的文件和目录。
元数据存储也是持久化的。整个文件系统名称空间(包括块到文件的映射以及文件系统属性)包含在一个名为FsImage的文件中,该文件保存在 NameNode的本地文件系统中。NameNode还使用事务日志来持久化地记录发生在文件系统元数据(元数据存储)中的每一次改动。该日志保存在 NameNode本地文件系统上的EditLog文件中。
从属NameNode
如前所述,HDFS的实现基于主/从架构。一方面,这种方式极大地简化了HDFS的整体架构。但另一方面,这也产生了一个故障单点,即NameNode失效事实上意味着HDFS失效。为从某种程度上缓解这一问题,Hadoop实现了从属NameNode。
从属NameNode不是一个“备用NameNode”。它不能接管主NameNode的功能。它为主NameNode提供检查点机制。除了保存 HDFS NameNode的状态之外,它还在磁盘上维护两个数据结构,用于持久化存储当前的文件系统状态;这两个数据结构分别是一个镜像文件和一个编辑日志。镜像文件代表HDFS元数据在某个时间点的状态,而编辑日志是一个事务日志(与数据库体系结构中的日志相比),记录了自从镜像文件创建之后文件系统元数据的每一次更改。
在启动(重启)的过程中,NameNode通过读取镜像文件并接着重播编辑日志来重建当前状态。显然,编辑日志越大,重播它所需要的时间就越长,因此启动NameNode所需要的时间也越长。为提升NameNode的启动性能,编辑日志会定期轮转,即通过将编辑日志应用于现有镜像来产生新的镜像文件。这个操作相当耗费资源。为最小化创建检查点所产生的影响并简化NameNode的功能,检查点的创建通常由运行在独立机器上的从属NameNode守护进程来完成。
作为检查点创建的结果,从属NameNode以前面所述镜像文件的格式保存了主NameNode持久化状态的一个(过期的)副本。在编辑日志保持相对较小的情况下,可以使用从属NameNode恢复文件系统的状态。要知道在这种情况下会有一定数量的元数据(以及对应的内容数据)丢失,因为保存在编辑日志中的最新更改是不可用的。
有一项正在进行的工作是创建一个真正的备份NameNode,它能够在主节点发生故障时实现接管。本章后面会讨论在Hadoop 2中引入的HDFS高可用性实现。
为保持NameNode的内存占用可控,HDFS块的默认大小为64MB——从数量级上大于多数其他块结构文件系统的块大小。大数据块的额外优势是允许HDFS将大量数据顺序地存储在磁盘上,以支持对数据进行高速流式读取。
HDFS中较小的块
关于Hadoop的误解之一是认为较小的块(小于块大小)在文件系统中仍然会占用整个块。事实并非如此。较小的块只占用它们所需要大小的磁盘空间。
但这并不意味着大量小文件可以有效地利用HDFS。无论块大小是多大,其元数据在NameNode中所占的内存完全相同。其结果是,数目众多的HDFS小文件(小于块大小)会占用大量的NameNode内存,从而给HDFS的可扩展性和性能带来负面影响。
在现实系统中,几乎不可能避免出现较小的HDFS块。比较大的可能性是某个给定的HDFS文件会占用一些完整的块和一些较小的块。这会成为一个问题吗?考虑到大多数HDFS文件会相当庞大,整个系统中此类较小块的数量将相对较少,因此通常没有问题。
HDFS文件组织的缺点是一个文件需要多个DataNode来提供服务,这意味着如果这些机器中的任何一台失效的话,该文件就变得不可用。为了避免此问题,HDFS在多台机器(默认为三台)上对每个块进行复制。
HDFS中数据复制的实现是写操作的一部分,采用数据管道的形式。当客户端向HDFS文件写入数据时,数据首先写入到本地文件。当本地文件积累到一整块数据时,客户端会向NameNode请求用于保存块副本的DataNode列表。客户端接着以4KB为单位将数据块从其本地存储写入到第一个 DataNode(参见图2-1)。该DataNode在本地文件系统中保存接收到的块,并将这部分数据转发到列表中的下一个DataNode。下一个接收到数据的DataNode重复相同的操作,直到副本集合中的最后一个节点接收到数据。这个DataNode仅在本地保存数据,不再进行转发。
在块写入的过程中,如果某个DataNode失效了,那么它将被从管道中移除。在这种情况下,当前块的写操作完成之后,NameNode会重新复制该块,以补偿由于DataNode失效而造成的副本缺失。当文件关闭时,临时本地文件中的剩余数据会通过管道发送到DataNode。客户端接下来通知 NameNode文件已关闭。此时,NameNode将此文件创建操作提交到持久化存储。如果NameNode在文件关闭之前发生崩溃,那么该文件会丢失。
块大小和复制因子的默认值由Hadoop配置指定,但可以以逐个文件为基础覆盖该默认值。应用程序可以指定块大小、副本数量以及特定文件在其创建时的复制因子。
HDFS最强大的特性之一就是对副本放置的优化,这对HDFS的可靠性和性能来说至关重要。NameNode负责做出块复制相关的所有决定,它会周期性地(每3秒钟)接收来自每个DataNode的心跳和块报告。心跳用于确保DataNode功能正常,而块报告可以验证DataNode上的块列表与 NameNode中的信息是否一致。DataNode在启动时要做的首要事情之一就是将块报告发送到NameNode。这允许NameNode迅速构建出整个集群中块分布的图景。
HDFS数据复制的最重要特性叫做机架感知。运行在计算机集群上的大型HDFS实例通常跨越许多个机架。通常情况下,相同机架上机器之间的网络带宽(以及与之相关联的网络性能)远大于不同机架上机器之间的网络带宽。
NameNode通过Hadoop机架感知进程确定各个DataNode所属的机架ID。一种简单的策略是将各个副本分别放置于不同的机架上。这种策略能够在整个机架失效时防止数据丢失,且将副本均匀地分布到集群中。它也允许在读取数据时使用源自多个机架的带宽。但由于在这种情况下,写操作必须将块传输到多个机架上,因此写入性能会受影响。
机架感知策略的一个优化方案是让占用的机架数少于副本数,以减少跨机架写入流量(进而提高写入性能)。例如,当复制因子为3时,将两个副本置于同一个机架上,并将第三个副本放在另一个不同的机架上。
为最小化全局带宽消耗和读取时延,HDFS会尝试从最靠近读取者的副本获取数据,以满足读请求。如果某个副本存在于与读取者节点相同的机架上,那么该副本将被用来响应读请求。
如前所述,每个DataNode会周期性地向NameNode发送心跳消息(参见图2-1),NameNode利用这些消息来发现DataNode 失效(基于心跳消息的缺失)。NameNode将最近没有心跳的DataNode标识为死机,并且不再向其派发任何新的I/O请求。由于位于死机 DataNode上的数据对HDFS不再可用,因此DataNode死机可能造成某些块的复制因子降低到其设定值之下。NameNode随时跟踪需要重新复制的块,并在必要时启动复制操作。
类似于多数其他现有文件系统,HDFS支持传统的层次化文件组织结构。它支持某个目录下文件的创建和删除、在不同目录之间移动文件等。它还支持用户配额和读/写权限。