一、HDFS体系结构
1、副本存放与读取策略
副本的存放是HDFS可靠性和性能的关键,HDFS采用一种称为机架感知的策略来改进数据的可靠性、可用性和网络带宽的利用率。
在大多数情况下,同一个机架内的两台机器间的带宽会比不同机架的两台机器间的带宽大。
带宽:固定时间可传输的资料数量,带宽越大越好。
HDFS采用的策略就是将副本存放在不同的机架上,这样可以有效防止整个机架失效时数据的丢失。在大多数情况下,副本系数是3,HDFS的存放策略是将一个副本存放在本地机架的节点上,另一个副本放在同一机架的另一个节点上,第三个副本放在不同机架的节点上。
机架的错误远比节点的错误少,所以这个策略不会影响数据的可靠性和可用性。因为数据块只放在两个不同的机架上,所以此策略减少了读取数据时需要的网络传输总带宽。这一策略在不损害数据可靠性和读取性能的情况下改进了写的性能。
2、安全模式
NameNode启动后会进入一个称为安全模式的特殊状态。处于安全模式的NameNode不会进行数据块的复制,NameNode从所有的DataNode接收心跳信号和快状态报告。
3、文件安全
第一种方法
将NameNode中的元数据转储到远程的NFS文件系统中。
第二种方法
系统中同步一个Secondary NameNode(二级NameNode)。
这个节点的主要作用就是周期性地合并编辑日志中的命名空间镜像,以避免编辑日志过大。需要注意的是,Secondary NameNode的同步备份总会滞后于NameNode,所以损失是必然的。
二、HDFS中的读写数据
1、文件的读取
(1)打开
客户端调用FileSystem对象中的open()函数来读取它需要的数据,FileSystem是HDFS中DistributedFileSystem的一个实例。
(2)获得块的位置
DistributedFileSystem会通过RPC协议调用NameNode来确定请求文件块所在的位置。
NameNode只会返回所调用文件中开始的几个块而不是全部返回,这些返回的DataNode会按照Hadoop定义的集群拓扑结构得出客户端的距离,然后再进行排序。
其次,DistributedFileSystem会向客户端返回一个输入流对象FSDataInputStream,用于给客户端读取数据。FSDataInputStream包含一个DFSInputStream对象,这个对象用来管理DataNode和NameNode之间的I/O。
(3)读取
客户端在这个输入流之上调用read()函数。DFSInputStream对象中包含文件开始部分数据块所在的DataNode地址,首先它会链接包含文件第一个块最近的DataNode。
(4)读取
随后,在数据流中重复调用read()函数,直到这个块全部读完为止。
(5)读取
当最后一个块读取完毕时,DFSInputStream会关闭链接,并查找存储下一个数据块距离客户端最近的DataNode。
(6)关闭
客户端按照DFSInputStream打开和DataNode连接返回的数据流的顺序读取该块,它也会调用NameNode来检索下一组块所在的DataNode的位置信息。
当完成所有文件的读取时,客户端则会在FSDataInputStream中调用close()函数。
设计要点
客户端通过NameNode引导获取最合适的DataNode地址,然后直接连接DataNode读取数据。
这种设计的好处在于:
- 可以使HDFS扩展到更大规模的客户端并行处理,这是因为数据的流动是在所有DataNode之间分散进行的。
- 同时,NameNode的压力也变小了,使得NameNode只用提供请求块所在的位置信息就可以了,而不用通过它提供数据,这样就避免了NameNode随着客户端数量的增长而成为系统瓶颈。
2、文件的写入
(1)创建
客户端通过调用DistributedFileSystem对象中的creat()函数创建一个文件。
(2)创建
DistributedFileSystem通过RPC调用在NameNode的文件系统命名空间中创建一个新文件,此时还没有相关的DataNode与之关联。
(3)写数据
NameNode会通过多种验证保证新的文件不存在文件系统中,并确保请求客户端拥有创建文件的权限。当所有验证通过时,NameNode会创建一个新文件的记录。
- 如果创建失败,则抛出一个IOException异常;
- 如果成功,则DistributedFileSystem返回一个FSDataOutputStream给客户端用来写入数据。
- FSDataOutputStream也包含一个数据流对象DFSOutputStream,客户端使用它来处理和DataNode及NameNode之间的通信。
(4)写文件包
当客户端写入数据时,DFSOutputStream会将文件分割成包,然后放入一个内部队列,我们称为“数据队列”。
DataStreamer的作用是请求NameNode为新的文件包分配合适的DataNode存放副本,返回的DataNode列表形成一个“管道”,假设副本数是3,则这个管道中就会有3个DataNode。
DataStreamer将文件包以流的方式传送给队列中的第一个DataNode,第一个DataNode会存储这个包,然后将它推送到第二个DataNode中,直到管道中的最后一个DataNode。
(5)返回确认信息
DFSOutputStream同时也会保存一个包的内部队列,用来等待管道中的DataNode返回确认信息,这个队列被称为确认队列。
只有当所有管道中的DataNode都返回了写入成功的返回信息文件包,才会从确认队列中删除。
(6)关闭
客户端成功完成数据写入的操作后,就会调用6种close()函数关闭数据流。
这步操作会在连接NameNode确认文件写入完全之前将所有剩下的文件包放入DataNode管道,等待通知确认信息。