HDFS 架构
假设和目标
硬件故障:硬件故障是正常现象而非异常。一个HDFS实例可能由成百上千台节点组成,每个节点都存储了一部分系统数据。事实上当有大量节点时,每个节点都有概率出故障,这意味着总是会有一些HDFS节点不能正常工作。因此,对故障的监控,以及当发生故障时能够快速自动的恢复就是HDFS的一个核心架构目标。
流式数据访问:运行在HDFS上的应用程序有时候需要流式访问数据集。但是HDFS的设计初衷主要是针对海量数据而不是和用户的交互。重点在于海量数据的高吞吐量而不是低延迟的数据访问。
海量数据集:运行于HDFS上的应用一般都会处理海量的数据集,因此,HDFS被设计用来支持海量的数据。一个集群中应该能够容纳成千上百的节点,而且能够有高带宽以聚合数据。
简单一致性模型:对于文件,HDFS需要一个一次写多次读的模型。文件在被创建、写入、关闭后,已有的内容不能再被修改,只能追加内容或清空。该假设简化了数据一致性问题,而且允许高吞吐量的数据访问。MapReduce 应用或网络爬虫应用都能完美地适用这个模型。
“移动计算比移动数据代价更低”:如果被应用请求的计算里数据集更近,那它的执行效率就会更高效。这在数据集很大时尤其明显。这能够最小化网络阻塞并提升整个系统的吞吐量。该假设是将运算迁移到离数据集更近的地方,比将数据迁移到运算所在的位置更好。HDFS提供了接口可以将运算迁移到离数据集更近的地方。
跨异构硬件和软件平台的便携性:HDFS一直被设计为很容易从一个平台移植到别的平台。这有助于广泛采用HDFS作为一组大型应用程序的平台。
NameNode 和 DataNode
HDFS 是 master/slave 架构。一个 HDFS 集群包含一个 NameNode——一个用来管理文件系统命名空间,以及控制来自客户端对文件的访问的 master server。除此之外,还有很多的 DataNode ,通常集群中的每个节点都是一个 DataNode,它们管理连接到它们所运行的节点的存储。HDFS 公开文件系统命名空间,并允许将用户数据存储在文件中。在内部,一个文件会被分成一个或多个块,这些块儿会被存储在一系列的 DataNode 节点上。NameNode 负责执行文件系统命名空间的操作,如打开、关闭、以及重命名文件和目录等。它也用来确定块文件与 DataNode 的映射。DataNode 负责为来自文件系统客户端的读写请求提供服务。DataNode 也会根据 NameNode 的指示来执行块的创建、删除和副本复制。
HDFS 架构NameNode 和 DataNode 是为运行在商用机器上而设计的软件。这些机器通常运行的是 GNU/Linux 操作系统。HDFS 是用 Java 语言构建的;任何支持 Java 语言的机器都可以运行 NameNode 和 DataNode 软件。使用高度可便携性的 Java 语言意味着 HDFS 可以被部署在各种各样的机器上。一个典型的部署方式是让一台机器专门用于部署 NameNode 软件。在集群中的别的机器,每台运行一个 DataNode 软件的实例。该架构没有排除在同一台机器上运行多个 DataNode 实例,但是在真实的部署中这种情况几乎很少。
集群中有一个 NameNode 的架构极大地简化了该系统的架构。NameNode 是仲裁者,也是所有 HDFS 元数据的存储仓库。所有用户数据绝不流经 NameNode 是该系统的设计。
文件系统命名空间
HDFS 支持传统的封层文件组织。用户或者应用可以在这些目录中创建目录和存储文件。该文件系统命名空间的分层和别的已存在的文件系统类似;用户可以创建、删除文件,将文件从一个目录移至另一个目录,或者重命名文件。HDFS 支持用户配额和访问权限。HDFS 不支持硬链接和软链接。然而,HDFS 架构并没有排除实现这些功能。
数据副本
HDFS 通过一个大的集群中的机器来可靠地存储海量的文件。它把每个文件存储为一系列的块。复制文件的块是为了故障转移(或者叫容错)。每个文件的块大小和副本因子是可配置的。
除了最后一个块之外, 一个文件的中的所有块的大小是一样的。然而,用户可以在添加对可变长度块的支持,以支持用户在不需要填满最后一个块就进行追加或 hsync。
应用可以指定一个文件的副本数。副本因子可以在文件创建时被指定,也可以在随后更改。HDFS 中的文件是一次写(除非追加或清空),在任何时候,有且只能有一个写。
NameNode 所有决定都根据块的副本数做出。它定期地接收来自于集群中所有 DataNode 节点的心跳和块报告。接收到心跳意味着 DataNode 在正常工作。块报告包含了 DataNode 上所有块的列表。
副本位置:第一步
对于 HDFS 的可靠性和性能而言,副本的位置是很关键的。优化副本的位置是 HDFS 与其他大多数分布式文件系统的一个很大的不同。这是一个需要大量调试和经验的功能。机架感知副本位置策略的目的是提高数据可靠性、可用性和网络带宽利用率。当前对于复本位置策略的实现,是在这个方向上的第一次努力。实现该策略的短期目标是,在生产系统中验证它,了解到更多关于它的行为,然后构建一个基础以测试和研究更复杂的策略。
大型的 HDFS 实例通常运行在一个由跨越多个机架组成集群上。在不同机架上的两个节点间的通信必须要通过交换机。大多数情况下,同一个机架上的机器间的带宽,比在不同机架上的机器间的带宽要大。
NameNode 通过 Hdoop Rack Awareness 中描述的进程,确定每一个 DataNode 属于哪个机架id。一个简单但并非最优的策略是将副本放置在不同的机架上。当整个机架出现故障时,这可以避免丢失数据,而且允许在读取数据时,使用多个机架的带宽。该策略将副本分布在集群中,这使它很容易在组件故障时均衡负载。然而,这个策略增加了写的成本,因为写需要在不同机架间传输数据块。
通常情况下,副本因子是3,HDFS 的布局策略是,将一个副本放置在本地机架上的一个节点上,另一个副本防止在本地机架上的另一个节点中,最后一个副本放置在不同机架上的节点中。这个策略减少了机架间的写通信量,提高了写性能。机架故障的概率小于节点故障的概率;该策略不会影响对数据可靠性和可用性的承诺。然而,当读取数据时,它确实减少了聚合网络带宽的使用,因为一个块仅仅防止在2个(而不是3个)不同的机架上。通过该策略,同一个文件的副本实现了最终没有跨机架分布的效果。1/3的副本在一个节点上,2/3的副本在一个机架上,另外1/3的副本最终分布在别的机架上。该策略提高了写性能却没有在数据可靠性和读性能上做出妥协。
当前,以上描述的副本位置策略是一个正在进行中的工作。
副本选择
为了最小化全局带宽消耗和读操作的延迟,HDFS 会努力满足一个来自于最接近读取器的副本的读请求。如果在读取节点的同一个机架上存在一个副本,那么该副本就会被优先选择以满足读请求。如果 HDFS 集群跨越多个数据中心,那么存在本地数据中心的副本将被优先选择。
安全模式
一旦开始,NameNode会进入一个特殊模式——安全模式。当处于安全模式时,不会发生数据块复制。此时,NameNode 会接收来自 DataNode 的心跳和BlockReport。BlockReport包括 DataNode 所在主机上的数据块列表。每个块都有指定一个副本的最小数量。当 NameNode 检测一个数据块最小的副本数量满足时,该块会被认为是已经有了足够保证安全的副本。在 NameNode 检测满足安全要求的数据块达到配置的数据块百分比时,NameNode 会退出安全模式。然后它会确定那些仍然少于指定数目的副本列表,并将它们复制到别的 DataNode。
文件系统元数据的持久化
HDFS 的 namespace 由 NameNode 存储。NameNode 使用一个叫 EditLog 的事务日志去记录每次对文件系统元数据的更改。例如,在 HDFS 中创建一个新的文件,就会在 EditLog 中插入一条指向该文件的记录。类似的,更改一个文件的副本因子也会造成在 EditLog 中插入一条新的记录。NameNode 使用一个其所在主机操作系统的文件系统上的文件来存储 EditLog。整个文件系统 namespace,包括块到文件的映射和文件系统属性,都存储在 FsImage 文件中。FsImage 也是作为 NameNode 本地文件系统的一个文件被存储。
NameNode 在内存中维护了一个镜像,该镜像包含整个文件系统 namespace 和块映射关系。这个关键的元数据项被设计的很袖珍,以至于 NameNode 只需要 4G RAM 就足够支持数量巨大的文件和目录。当 NameNode 启动时,它会从硬盘读取 FsImage 和 EditLog,将所有来自于 EditLog 的事务应用到 FsImage 的内存镜像。然后将现在的新版本镜像刷出到硬盘上,存在一个新的 FsImage 文件中。随后,旧的 EditLog 会被清空,因为它的事务有已经被应用到了持久化的 FsImage 中。这个过程叫 checkpoint。在当前的实现中,checkpoint 机制仅仅会在 NameNode 启动时发生。正在进行中的工作是希望实现,在不久的将来,支持定期的 checkpointing。
DataNode 在其本地文件系统中存储 HDFS 的数据。DataNode 不知道关于 HDFS 文件的事情。它在本地文件系统中,将 HDFS 数据块存储在一个一个的文件中。DataNode 不会在同一个目录中创建所有文件。事实上,它会启发式地去决定每个目录中最佳的文件数量,并适当地创建子目录。在同一个目录中创建所有本地文件不是最优的,因为本地文件系统可能不能够有效支持在单个目录中创建大量的文件。当 DataNode 启动时,它会扫描本地文件系统,产生一个与所有本地文件相对应的 HDFS 数据块的列表,并将该报告发送给 NameNode:这就是 BlockReport。
通信协议
所有 HDFS 通信协议都在 TCP/IP 协议之上。客户端会与在 NameNode 机器上配置的 TCP 端口建立连接,它用 NameNode 和客户端协议通信。DataNode 使用 DataNode 端协议与 NameNode 通信。远程调用抽象封装了客户端协议和 DataNode 端协议。按照设计,NameNode 不会发起任何远程调用。事实上,它只会响应由 DataNode 或者客户端发射出的远程调用请求。
鲁棒性
HDFS 最主要的目标就是能够可靠地存储数据,哪怕是在出现故障的情况下。三种常见的故障类型是 NameNode 故障,DataNode 故障和网络部分的故障。
数据磁盘故障,心跳监控和重新复制副本
每个 DataNode 都会周期性地给 NameNode 发送一个心跳信息,网络不稳定可能会导致某个(些) DataNode 失去与 NameNode 的连接。NameNode 通过心跳信息来监控这种情况。NameNode 会将没有接收到心跳信息的那些 DataNode 标记为“dead”,此后就不会再转发任何的 IO 信息给它(们)。对HDFS而言,任何注册在死亡节点上的信息都不可用。因此,DataNode 的死亡可能会导致某些块的副本因子小于指定值。NameNode 会不断地追踪哪些块需要复制副本并在必要的时候进行复制。由于很多原因,副本有时必须的再复制,如:DataNode 死亡,副本被损坏,DataNode 的硬盘出现故障,或者文件的副本因子增大等等。
为避免由 DataNode 状态抖动引起的副本复制风暴,很保守地将标记 DataNode 死亡的超时时间设为了10分钟。用户可以通过配置为性能敏感的工作负载,将不合格 DataNode 标记为弃用状态的时间间隔设置的更短,以避免对弃用节点有更多的读写。
集群再平衡
HDFS架构和数据在平衡方案兼容。当一个 DataNode 节点的剩余可用空间低于某个阈值时,该方案可以自动将该节点的数据移到别的节点。如果某个特殊的文件忽然需要很大的空间,有一种方案可以动态地创建额外的副本并再平衡集群中的数据。这些数据再平衡类型的方案至今还没有实现。——》》》???至今没有实现???
数据完整性
从 DataNode 获取的数据块有可能损坏。这种损坏可能是因为存储设备的故障,网络故障,或者软件漏洞。HDFS客户端软件实现了校验和以校验 HDFS 文件的内容。当客户端创建 HDFS 文件时,它会计算文件的块的校验和并将其存储在同一个 HDFS 命名空间下的一个独立的隐藏文件中。当客户端检索文件内容时,它会验证从每个 DataNode 检索到的数据和存储在校验和文件中的数据是否匹配。如果不匹配,那么客户端会去另一个存储了该块副本的 DataNode 中检索。
元数据磁盘故障
FSImage 和 Editlog 是 HDFS 架构的核心文件。这些文件的损坏可能会导致 HDFS 无法正常工作。因为这个原因,NameNode 可以配置为维护多个 FsImage 和 Editlog 的副本。任何对 FsImage 或者 Editlog 的更新都会导致所有 FsImage 和 Editlog 文件的更新。该同步更新可能会降低 NameNode 每秒支持的 namespace 事务的速率。然而这个降低是可以接受的,因为虽然 HDFS 本质上是数据密集的,但它并不是元数据密集的。当 NameNode 重启时,它会使用最新一致的 FsImage 和 Editlog。
快照
快照支持存储数据在某个特定时刻的副本。使用快照的功能是可以在 HDFS 实例损坏时将其回滚到先前某个状态良好的时刻。
数据结构
数据块
HDFS 的设计可以支持海量的数据。和 HFDS 兼容的应用是那些能够处理大数据集的应用。这些应用仅仅会写一次数据,但是它们会一次或多次读取,而且要求读能够满足流式读取。HDFS 支持对文件的一次写多次读语义。HDFS 使用的块大小通常是 128MB。因此,一个 HDFS 文件会被分割成多个 128MB 的块,而且如果可能的话,每个块将被放置在不同的 DataNode 上。
Staging
来自于客户端的创建文件请求不会马上就发送到 NameNode。事实上,HDFS 客户端首先会将文件数据缓存到本地缓冲池。 应用写被透明地重定向到这个本地缓冲中。当本地文件累积的数据超过一个块的大小时,客户端才会联系 NameNode。NameNode 将该文件名插入到文件系统的层级中,并为其分配一个数据块。NameNode 使用 DataNode 的和目标数据块的数据标识响应客户端请求。然后客户端会将该块数据从本地缓冲中刷出到指定的 DataNode。当文件被关闭时,剩下的在本地缓冲中没被刷出的数据将将被传输到指定的 DataNode。随后,客户端将告知 NameNode 该文件已被关闭。
经过仔细慎重地考虑了运行在 HDFS 上的应用之后,以上方法已被采用。这些应用程序需要流式写文件。如果客户端直接写到一个远程文件而没有经过任何客户端缓冲,那网络速度和阻塞将会产生相当大的影响;这种做法并没有先例。早期的分布式文件系统,如 AFS,已经使用了客户端缓存来提高性能。POSIX 要求已经被放宽以实现更高性能的数据上传。
副本流水线
正如上一部分所述,当客户端往 HDFS 上写文件时,它的数据首先会被写到本地缓存。假设 HDFS 的副本因子为3。当本地缓存累积够1个一个用户数据块后,客户端会从 NameNode 检索一个 DataNode 列表。此列表包含那个数据块将被刷出到的 DataNode。然后,客户端会将该数据块刷出到第一个 DataNode。第一个 DataNode 开始一小部分一小部分地接收数据,然后将每一部分写到它的本地存储库,然后再将这一小部分传输到列表中的第二个 DataNode。因此,DataNode 可以从流水线中先前的 DataNode 接收数据,同时转发数据到流水线中的下一个 DataNode。因此,数据就像是在流水线上一样被传输,从一个 DataNode 到另一个 DataNode。
易得性
应用可以通过多种不同的方式访问 HDFS 上的数据。HDFS 为应用提供了原生的 FileSystem Java API,也提供了一个 C language wrapper for this Java API 和 REST API。此外, 也可以用 HTTP 浏览器浏览 HDFS 实例的文件。通过使用 NFS gateway,HDFS 可以作为本地系统的一部分被挂载。
FS Shell
HDFS 允许用户以文件或目录的形式组织数据。它提供了一个命令行接口——Fs Shell,可以让用户和 HDFS 中的数据进行交互。这个命令集的语法和其他用户很熟悉的 shells 很像,(如,bash,csh)。下面是几个 动作/命令 的例子:
Action Command
创建一个叫 /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 管理员使用。下面是几个 动作/命令 的例子:
Action Command
让集群进入安全模式 bin/hdfs dfsadmin -safemode enter
生成包含所有 DataNode 的列表 bin/hdfs dfsadmin -report
重新委任或解除 DataNode(s) bin/hdfs dfsadmin -refreshNodes
浏览接口
典型的 HDFS 安装会通过一个可配置的 TCP 端口配置一个 web 服务以显示 HDFS 的命名空间。这允许用户导航 HDFS 命名空间并使用浏览器访问它的文件目录。
空间回收
文件删除和撤销删除
如果启用了垃圾箱配置,被 Fs Shell 删除掉的文件并不会马上从 HDFS 删除。事实上,HDFS 会将其移至垃圾箱目录(每个用户都有一个他自己的垃圾箱目录,/user/<username>/.Trash)。只要文件还在垃圾箱,就可以被快速地还原。
大多数最近被删除的文件都会被移动到最新的垃圾箱目录(/user/<username>/.Trash/Current),在可配置的区间内,HDFS 会为在当前垃圾箱(在 /user/<username>/.Trash/<date> 下)中的文件创建 checkpoints,并在过期时删除旧的 checkpoints。有关垃圾箱的 checkpointing,参见 expunge command of FS shell。
在垃圾箱中文件的过期时间达到时,NameNode 会删除掉来自于 HDFS namespace 的文件。该删除会造成相对应的块文件的释放。注意,在用户删除一个文件时会有一点延迟,在这个过程中 HDFS 会增加对应的空间。
下面的例子展示了如何用 Fs Shell 删除 HDFS 上的文件。我们在目录 delete 下创建了两个文件(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 会将多余的副本删掉。接下来的一次心跳会将该信息传送给 NameNode。随后,DataNode 会删除对应的块,相应会在集群中多出来释放的空间。同样地,在完成调用 setReplication API 和在集群中显示出释放空间的过程中可能会有一个延迟。
参考资料
Hadoop JavaDoc API
HDFS source code: http://hadoop.apache.org/version_control.html