什么是HDFS

HDFS(hadoop 分布式文件系统),HDFS 是一个分布式的、高容错、高吞吐量的海量数据存储解决方案。

HDFS体系结构

HDFS是一个 master/slave 体系结构的分布式系统。HDFS集群拥有一个NameNode和一些DataNode, 用户可以通过HDFS客户端同NameNode和DataNode交互以访问文件系统。其体系结构如下图所示:

HDFS设计架构 hdfs架构图流程_HDFS设计架构

1、NameNode(管理节点)

NameNode负责管理HDFS文件系统的元数据(文件系统目录树等)、命名空间以及数据块映射信息,如那个文件分了多少个数据块,这些数据块又分布在那些DataNode节点等等这些信息。

NameNode负责管理DataNode节点,包括DataNode节点的上线和下线等。

NameNode 负责响应客户端的请求,然后指导DataNode处理文件的读写。

2、Rack:(机架):

每个机架下面存在多个DataNode节点。

3、Datanode(数据节点)

DataNode负责本地数据块的存储和管理,DataNode会不断地向NameNode发送心跳,汇报当前节点的数据块信息、缓存信息。NameNode节点也会向DataNode节点发送创建、删除文件的指令。

4、Blocks(数据块)

数据块是HDFS文件存储的最小单元。由于HDFS文件往往比较大,为了最小化寻址开销,所以HDFS的数据块也是比较大的,默认为128M。数据块会以文件的形式存储到数据节点的磁盘中。

在HDFS中,所有的文件都会被分成若干个数据块分布在各个节点上。为了防止数据丢失,每个数据块会存在若干个副本(默认为3副本),分布在不同的节点上。所以一个副本的丢失并不会影响数据的访问。

5、HDFS通信协议

HDFS作为一个分布式系统。其流程是相当复杂的,常常涉及很多的节点。为了降低代码之间的耦合性,HDFS将各种调用抽象成了不同的接口。HDFS中的接口主要有如下两种类型:

1、RPC接口:基于Hadoop RPC框架实现的调用接口。

2、流式接口:基于TCP或者HTTP实现的接口。

6、Client(客户端)

HDFS提供了各种各样的接口供用户使用,如命令行接口、浏览器接口、API接口。用户通过这些接口可以很方便的访问HDFS,而不需要考虑HDFS的具体实现细节。

HDFS 客户端读流程

Hdfs客户端读取文件的流程如下所示:

HDFS设计架构 hdfs架构图流程_数据块_02

1、HDFS客户端首先通过 DistributedFileSystem.open()方法和nameNode节点建立连接。获取文件起始块的位置。NameNode会选择距离客户端最近的DataNode副本返回(通过机架感知实现)。

2、连接到DataNode读取数据块。当前数据块读取完毕时,客户端会从NameNode获取下一个数据块的位置,读取下一个数据块。

3、关闭输入流。

在整个读取流程中,NameNode只需要告诉客户端块的位置(这些信息存储在内存中,因而非常高效),因此读取的效率是非常高的。

 

HDFS 客户端写流程

HDFS客户端文件写入的流程大致如下:

 

HDFS设计架构 hdfs架构图流程_架构_03

1、客户端调用DistributedFileSystem对象create()函数创建文件。

2、DistributedFileSystem对NameNode进行RPC调用,在文件系统的命名空间中新建一个文件,但没有相应的数据块与文件关联,即还没有相关的DataNode与之关联。

3、NameNode会执行相关的检查以确保文件系统中不存在该文件,并确认客户端有创建文件的权限。当所有验证通过后,NameNode会创建一个新文件的记录,否则,文件创建失败并向客户端抛出一个IOException异常。如果创建成功,则DistributedFileSystem向客户端返回一个FSDataOutputStream对象,客户端使用该对象向HDFS写入数据,同样地,FSDataOutputStream封装着一个DFSOutPutStream数据流对象,负责处理NameNode和DataNode之间的通信。

4、当客户端写入数据时,DFSOut Put Stream会将文件分割成多个数据包,并写入一个数据队列中。Data Streamer负责处理数据队列,会将这些数据包放入到数据流中,并向NameNode请求为新的文件分配合适的DataNode存放副本,返回的DataNode列表形成一个管道,假设副本数为3,那么管道中就会有三个DataNode,Data Streamer将数据包以流的方式传送给队列中的第一个DataNode,第一个DataNode会存储这个数据包,然后将它推送到第二个DataNode中,然后第二个DataNode存储该数据包并且推送给管道中的第三个DataNode。

5、DFSOutputStream同时维护着一个内部数据包队列来等待DataNode返回确认信息,被称为确认队列。只有当管道中所有的DataNode都返回了写入成功的信息后,该数据包才会从确认队列中删除。

6、客户端成功完成数据写入操作以后,对数据流调用close()函数,该操作将剩余的所有数据包写入DataNode管道,并连接NameNode节点,等待通知确认信息。

如果在数据写入期间DataNode发送故障,HDFS就会执行以下操作。

1、首先关闭管道,任何在确认队列中(在确认队列中,说明还没有收到确认消息)的数据包都会被添加到数据队列的前端,以保证管道中失败的DataNode的数据包不会丢失。当前存放在正常工作的DataNode上的数据块会被制定一个新的标识,并和NameNode进行关联,以便故障DataNode在恢复后可以删除存储的部分数据块。

2、然后,管道会把失败的DataNode删除,文件会继续被写到另外两个DataNode中。

3、最后,NameNode会注意到现在的数据块副本没有达到配置属性要求,会在另外的DataNode上重新安排创建一个副本,后续的数据块继续正常接收处理。

在一个块被写入期间,可能会有多个DataNoe发生故障(这种情况非常少见)。只要写入了dfs.namenode.replication.min 的副本数(默认为1)。写操作就会成功。

HDFS 客户端追加写流程

Hdfs 追加写的流程和 hdfs写入流程是非常相似的,只有在下面两个步骤上有少许差别:

1、客户端调用DistributedFileSystem对象append()函数打开一个文件,获取文件最后一个数据块的信息。

2、建立数据管道的时候DFSOutStream会判断最后一个数据块是否已经写满。如果没有写满,则建立起到该数据块的数据流管道。如果已经写满,则addBlock()向namenode重新申请一个空数据块之后建立数据流管道。

DataNode启动、心跳以及执行NameNode指令流程

看一下Datanode和Namenode的交互流程,包括Datanode启动时的注册流程、心跳流程、数据块汇报以及增量汇报等流程,如下图所示:

 

HDFS设计架构 hdfs架构图流程_hadoop_04

Datanode启动后与Namenode的交互主要包括三个部分,握手、注册、块汇报以及缓存汇报。

1、Datanode启动后会首先通过DatanodeProtocol.versionRequest()获取Namenode的版本号以及存储信息等,然后Datanode会对Namenode的当前版本号和Datanode的当前软件、版本号进行比较,确保他们是一致的。

2、成功地完成握手操作后,Datanode会通过DatanodeProtocol.registerDatanode()方法向Namenode注册。Namenode接收到注册请求后,会判断当前Datanode的配置是否属于这个集群,他们之间的版本是否一致。

3、注册成功之后,Datanode就需要将本地存储的所有数据块以及缓存的数据块上报到Namenode,Namenode会利用这些信息重新建立内存中数据块与Datanode之间的对应关系。

至此,Datanode就完成了启动的所有操作,之后就可以正常对外服务了。

Datanode成功启动后,需要定期向Namenode发送心跳,让Namenode知道当前Datanode处于活动状态能够对外服务。Namenode会在Datanode的心跳响应中携带Namenode指令,指导Datanode进行数据块的复制、删除以及恢复等操作。

当Datanode成功地添加了一个新的数据块或者删除了一个已有的数据块时,需要通过DatanodeProtocol.blockReceivedAndDeleted()方法向Namenode汇报。Namenode接收到这个汇报之后,会更新Namenode内存中数据块与Datanode之间的对应关系。