HDFS 体系结构指南
1. 简介
HDFS是一个高容错和可部署在廉价机器上的系统。HDFS提供高吞吐数据能力适合处理大量数据。HDFS松散了一些需求使得支持流式传输。HDFS原本是为Apache Butch的搜索引擎设计的,现在是Apache Hadoop项目的子项目。
HDFS以流式数据访问模式(一次写入、多次读取)来存储超大文件,运行于商用硬件集群上。
2. HDFS设计目标
- 硬件失效:HDFS实例可能包含上百成千个服务器,每个节点存储着文件系统的部分数据。事实是集群有大量的节点,而每个节点都存在一定的概率失效也就意味着HDFS的一些组成部分经常失效。因此,检测错误、快速和自动恢复是HDFS的核心架构。
- 流式访问:应用运行在HDFS需要允许流式访问它的数据集。HDFS是被设计用于批量处理而非用户交互。设计的重点是高吞吐量访问而不是低延迟数据访问。
- 大数据集:HDFS是用来支持大文件。它应该提供高带宽和可扩展到上百节点在一个集群中。它应该支持在一个实例中有以千万计的文件数。
- 简单一致性模型:HDFS应用需要一个一次写入多次读取的文件访问模型。一个文件一旦创建,写入和关系都不需要改变。支持在文件的末端进行追加数据而不支持在文件的任意位置进行修改。
- 移动计算比移动数据更划算:如果应用的计算在它要操作的数据附近执行那就会更高效。尤其是数据集非常大的时候。这将最大限度地减少网络拥堵和提高系统的吞吐量。
- 轻便的跨异构的软硬件平台:DFS被设计成可轻便从一个平台跨到另一个平台。
3. HDFS不擅长的事情
- 低时间延迟的数据访问:要求低时间延迟数据访问的应用,例如几十毫秒范围,不适合在HDFS上运行。目前,对于低延迟的访问需求,HBase是更好的选择。
- 大量的小文件:由于namenode将文件系统的元数据存储在内存中,因此该文件系统所能存储的文件总数受限于namenode的内存容量。
- 多用户写入,任意修改文件:他不支持具有多个写入者的操作,也不支持在文件的任意位置进行修改。
4. HDFS的概念
- 数据块:HDFS中的块的大小默认设置为64MB,但是很多情况下HDFS使用128MB的块设置。HDFS中小于一个块大小的文件不会占据整个块的空间。HDFS的块设置的这么大(比磁盘的块大的多)的目的是最小化寻址开销。但是也不宜太大,因为MapReduce中的Map任务通常一次只处理一个块中的数据,因此如果任务数太少(少于集群中的节点数量),作业的运行速度就会比较慢。
5. NameNode and DataNodes
HDFS具有主/从架构。
- NameNode的工作: 一个HDFS集群包含一个NameNode,是一个主服务器,它用于管理文件系统名称空间并管理客户端对文件的访问。NameNode执行文件系统命名空间操作,如打开,关闭和重命名文件和目录。 它还确定块到DataNode的映射。 NameNode是所有HDFS元数据的仲裁者和存储库。 该系统的设计方式是用户数据永远不会流经NameNode。NameNode控制着关于blocks复制的所有决定。它周期性地接收集群中DataNode发送的心跳和块报告。收到心跳意味着DataNode在正常地运行着。一个块报告包含着DataNode上所有块信息的集合。
- DataNode的工作:通常是群集中的每个节点一个DataNode,用于管理连接到它们所运行的节点的存储。 HDFS公开文件系统名称空间并允许用户数据存储在文件中。 在内部,文件被分成一个或多个块,这些块存储在一组DataNode中。 DataNode负责提供来自文件系统客户端的读取和写入请求。 DataNode还根据来自NameNode的指令执行数据块创建,删除和复制。
- namenode的容错:(1)第一种机制是备份那些组成文件系统元数据持久状态的文件。这些写操作是实时同步的,是原子操作。(2)另一种可信的办法是运行一个辅助namenode,但它不能被用作namenode。这个辅助namenode的重要作用是定期通过编辑日志合并命名空间镜像,以防止编辑日志过大。但是,辅助namenode保存的状态总是滞后于主节点,所以在主节点全部失效时,难免会丢失部分数据。
- namenode的高可用性:通过配置了一对活动-备用(active-standby)namenode。当活动namenode失效,备用namenode就会接管他的任务并开始服务于来自客户端的请求,不会有任何明显中断。
6. 副本选址策略
namenode如何选择在那个datanode存储副本,这里需要对可靠性、写入带宽和读取带宽进行权衡。
副本的选址对HDFS的可靠性和性能是起到关键作用的。机架感知副本配置策略的目的是提高可靠性、可用性和网络带宽的利用率。运行在集群计算机的大型HDFS实例一般是分布在许多机架上。两个不同机架上的节点的通讯必须经过交换机。在大多数情况下,同一个机架上的不同机器之间的网络带宽要优于不同机架上的机器的。
通常情况下,当复制因子为3时,HDFS的副本放置策略是将一个副本放在本机架的一个节点上,将另一个副本放在本机架的另一个节点,最后一个副本放在不同机架的不同节点上。该策略减少机架内部的传输以提高写的性能。这个策略提高了写性能而不影响数据可靠性和读性能。为了最大限度地减少全局带宽消耗和读取延迟,HDFS试图让读取者的读取需求离副本最近。
7. 安全模式介绍
在启动时,NameNode进入一个特殊的状态称之为安全模式。当NameNode进入安全模式之后数据块的复制将不会发生。NameNode接收来自DataNode的心跳和数据块报告。数据块报告包含正在运行的DataNode上的数据块信息集合。每个块都指定了最小副本数。一个数据块如果被NameNode检查确保它满足最小副本数,那么它被认为是安全的。
NameNode存储着HDFS的命名空间。NmaeNode使用一个称之为EditLog的事务日志持续地记录发生在文件系统元数据的每一个改变。NameNode在它本地的系统中用一个文件来存储EditLog。整个文件系统命名空间,包括blocks的映射关系和文件系统属性,将储存在一个叫FsImage的文件。FsImage也是储存在NameNode所在的本地文件系统中。
NameNode在内存中保存着整个文件系统命名空间的图像和文件映射关系。当NameNode启动时,它将从磁盘中读取FsImage和EditLog,将EditLog中所有汇报更新到内存中的FsImage中,刷新输出一个新版本的FsImage到磁盘中。然后缩短EditLog因为它的事务汇报已经更新到持久化的FsImage中。这个过程称之为检查站。只有当NameNode启动时会执行一次。
8. 鲁棒性介绍
HDFS的主要目标是在失效出现时保证储存的数据的可靠性。通常有这三种失效,分别为NameNode失效,DataNode失效和网络分裂(一种在系统的任何两个组之间的所有网络连接同时发生故障后所出现的情况)
- 心跳机制:每个节点周期性地发送心跳信息给NameNode。网络分裂会导致一部分DataNode失去与NameNode的连接。NameNode通过心跳信息的丢失发现这个情况。NameNode将最近没有心跳信息的DataNode标记为死亡并且不再转发任何IO请求给他们。在已经死亡的DataNode注册的任何数据在HDFS将不能再使用。DataNode死亡会导致部分数据块的复制因子小于指定的数目。NameNode时常地跟踪数据块是否需要被复制和当必要的时候启动复制。
- FsImage和EditLog是HDFS架构的中心数据。这些数据的失效会引起HDFS实例失效。因为这个原因,NameNode可以配置用来维持FsImage和EditLog的多个副本。FsImage或EditLog的任何改变会引起每一份FsImage和EditLog同步更新。
9. HDFS读写文件流程
1. 读文件流程:
客户端将要读取的文件路径发送给namenode,namenode获取文件的元信息(主要是block的存放位置信息)返回给客户端,客户端根据返回的信息找到相应datanode逐个获取文件的block并在客户端本地进行数据追加合并从而获得整个文件。
详细步骤:
1.初始化FileSystem,然后客户端(client)用FileSystem的open()函数打开文件。
2.FileSystem用RPC调用元数据节点,得到文件的数据块信息,对于每一个数据块,元数据节点返回保存数据块的数据节点的地址。
3.FileSystem返回FSDataInputStream给客户端,用来读取数据,客户端调用stream的read()函数开始读取数据。
4.DFSInputStream连接保存此文件第一个数据块的最近的数据节点,data从数据节点读到客户端(client)
5.当此数据块读取完毕时,DFSInputStream关闭和此数据节点的连接,然后连接此文件下一个数据块的最近的数据节点。
6.当客户端读取完毕数据的时候,调用FSDataInputStream的close函数。
7.在读取数据的过程中,如果客户端在与数据节点通信出现错误,则尝试连接包含此数据块的下一个数据节点。
- 失败的数据节点将被记录,以后不再连接。
2. 写文件流程:
客户端要向HDFS写数据,首先要跟namenode通信以确认可以写文件并获得接收文件block的datanode,然后,客户端按顺序将文件逐个block传递给相应datanode,并由接收到block的datanode负责向其他datanode复制block的副本。
详细步骤:
1.初始化FileSystem,客户端调用create()来创建文件。
2.FileSystem用RPC调用元数据节点,在文件系统的命名空间中创建一个新的文件,元数据节点首先确定文件原来不存在,并且客户端有创建文件的权限,然后创建新文件。
3.FileSystem返回DFSOutputStream,客户端用于写数据,客户端开始写入数据。
4.DFSOutputStream将数据分成块,写入data queue。data queue由Data Streamer读取,并通知元数据节点分配数据节点,用来存储数据块(每块默认复制3块)。分配的数据节点放在一个pipeline里。Data Streamer将数据块写入pipeline中的第一个数据节点。第一个数据节点将数据块发送给第二个数据节点。第二个数据节点将数据发送给第三个数据节点。
5.DFSOutputStream为发出去的数据块保存了ack queue,等待pipeline中的数据节点告知数据已经写入成功。
6.当客户端结束写入数据,则调用stream的close函数。此操作将所有的数据块写入pipeline中的数据节点,并等待ack queue返回成功。最后通知元数据节点写入完毕。
7.如果数据节点在写入的过程中失败,关闭pipeline,将ack queue中的数据块放入data queue的开始,当前的数据块在已经写入的数据节点中被元数据节点赋予新的标示,则错误节点重启后能够察觉其数据块是过时的,会被删除。失败的数据节点从pipeline中移除,另外的数据块则写入pipeline中的另外两个数据节点。元数据节点则被通知此数据块是复制块数不足,将来会再创建第三份备份。
8.如果在写的过程中某个datanode发生错误,会采取以下几步:
1)pipeline被关闭掉;
2)为了防止防止丢包ack quene里的packet会同步到data quene里;
3)把产生错误的datanode上当前在写但未完成的block删掉;
4)block剩下的部分被写到剩下的两个正常的datanode中;
5)namenode找到另外的datanode去创建这个块的复制。当然,这些操作对客户端来说是无感知的。