HDFS读写原理

Hadoop 是什么

Hadoop 是一个开源的大数据框架同时也是一个分布式计算的解决方案。Hadoop = HDFS (分布式文件系统)+MapReduce(分布式计算)

HDFS

HDFS 概念

  1. 数据块

  2. NameNode

  3. DataNode

数据块:

数据块是一个抽象的块,而不是整个文件。默认大小是64Mb,一般设置为128Mb,备份x3 数据块的大小可以随着磁盘传输速率的提升而得到增加。

HDFS的块比磁盘的大,主要是减少寻址时间在整个文件传输时间中的占比。比如为了让磁盘寻址时间只占到整个文件传输时间的1%,而寻址时间为10ms,磁盘的IO传输速率为100Mb/s,那么一个块的大小要大于100Mb才能达到这个要求。随着以后磁盘的传输速率越来越高,块的大小也会越来越大的。 【但是块的大小也不会很大,因为MapReduce中的map任务一次只处理一个块的数据,如果map任务过少(少于集群的节点数量),作业的运行效率也会比较慢】

对数据块进行抽象的好处:
  • 一个文件的大小可以大于集群网络中任一个磁盘的大小。因为可以对文件进行分块存储,所以在一种极端情况下,一个集群只存放了一个文件,该文件占满了集群中的所有磁盘。

  • 使用抽象块而不是整个文件作为存储单元,可以简化存储子系统的设计。首先块的大小是固定的,所以一个磁盘能够存储多少个块很容易就能够计算出来。另外也消除了对于元数据的顾虑,块只是要存储的大块数据,而文件的元数据,例如权限信息等等,并不需要与块进行一同存储,可以进行单独管理。

  • 块适合于提供备份和冗余容错的作用,通过将块进行复制副本,通常是3个,当因损坏或者机器故障而丢失的块,我们便可以从其他候选机器将副本块复制到另一台能够正常工作的机器上,保证副本的数量保持不变。


HDFS集群有两类节点,以管理节点-工作节点的模式运行着,分别是NameNode和DataNode。

NameNode

NameNode :

  1. 管理文件系统的命名空间,存放着元数据 (对应文件:命名空间镜像文件

  2. 维护着文件系统树以及整棵树内所有的文件以及目录 (对应文件:编辑日志

  3. 记录着每个文件各个块所在的数据节点信息,但并不会一直保存块的位置信息,因为在重启时会根据数据节点信息重建。

联邦HDFS

NameNode节点在内存中保存着文件系统中每个文件和每个数据块的引用关系,所以NameNode的内存大小会成为集群扩展的一个瓶颈。

在Hadoop 2.x版本中引入了联邦HDFS,允许在集群中添加多个NameNode节点,以实现扩展。

在联邦环境下,每个NameNode都维护着一个命名空间卷( 比如NameNode_1负责 /usr NameNode_2 负责 /share ),由命名空间的元数据和数据块池组成。

数据块池里面存放着该命名空间下所有的数据块。

命名空间卷之间不进行通信,甚至其中一个挂掉也不会影响另一个。但每个DataNode需要注册到每一个NameNode上,也需要存储着来自各个数据块池的数据块。

 

DataNode

  1. 存储以及检索数据块

  2. 向NameNode更新所存储块的列表


NameNode容错

若NameNode失效,则我们无法访问到文件系统上的所有文件。因为我们不知道怎么去根据DataNode的块去重建文件。因此需要对NameNode进行容错处理。

两种容错机制:

  • 对于组成文件系统元数据持久状态的文件 我们可以使NameNode在多个文件系统上对其进行保存,比如最常用的就是在将持久状态写入本地磁盘的同时也将其写入到远程挂载的NFS网络文件系统中。

  • 运行一个辅助NameNode节点 (Secondary NameNode)该节点却不能被用作NameNode,它的主要作用是定期合并编辑日志文件和命名空间镜像文件,防止其过大。它在合并后会生成命名空间镜像文件的副本,当NameNode失效时会启用。但是辅助NameNode节点保存的信息总是会滞后于NameNode节点,所以如果想要实现容错机制,可以在主NameNode节点失效后,将保存在NFS的文件系统元数据复制到Secondary NamoNode上来,将其作为新的主NameNode运行。

HA高可用


HDFS 优点

  • 适合大文件存储,支持TB、PB级文件的存储

  • 可以构建在廉价的机器上,并能够提供容错机制和恢复机制

  • 支持流式数据访问,一次写入,多次读取更高效

HDFS 缺点

  • 不适合大量小文件存储

  • 不适合并发写入,也不支持文件随机修改

  • 不支持随机读等低延迟的访问方式

 

HDFS 读写流程

HDFS 读流程

HDFS读流程图 (图片来源-慕课网)

 

假设现在有三个DataNode节点,分别存放着数据Data数据块1、数据块2、数据块1、2,则如果此时客户端想要请求Data数据,流程如下:

  1. Client向NameNode发出请求,请求Data文件。

  2. NameNode通过其所维护的相关的数据块的信息,会把该Data文件的所有block的所有的DataNode信息返回给Client。

  3. 然后Client随即从距离(按照带宽进行计算出来的距离) 最近且保存着文件第一个块的DataNode节点上读取数据

  4. 在读取完第一个块的数据以后,便会寻找下一个块的最佳DataNode,并从其上读取数据,直到将所有的Data文件块数据读取完毕

  5. 如果在读取的过程中,遇到问题(比如读取的DataNode节点挂掉了),则客户端会再次去寻找存有该块信息副本的DataNode节点,并从其上读取出块数据。

如下图所示:

然而上述的流程有些地方不够完善。客户端是怎么去读取块的数据的?读过程对于客户端而言是否是透明的?依据距离去寻找最优的DataNode节点,这个距离是如何计算的?

详细的读流程图如下所示:

客户端如何读取DataNode上所存的块数据?

步骤一:客户端通过FileSyste对象的open()方法去打开希望读取的文件;

步骤二:此时,DistributedFileSystem会通过RPC去调用NameNode,然后NameNode会返回存有该文件所有block的所有的DataNode信息 (对于每一个块,NameNode会返回存有该块副本的DataNode信息,并按照距离对DataNode进行排序);

步骤三:然后,DistributedFileSystem类会返回一个FSDataInputStream对象给客户端去读取数据

  • 该FSDataInputStream类会封装DFSInputStream对象,该对象管理着datanode和namenode的I/O

步骤四:客户端对这个数据流调用read()方法去读取块数据:

  • DFSInputStream里存放着文件前几个块的副本的所有DataNode地址,于是会去连接距离最近的DataNode

步骤五:Client通过对数据流反复调用read()方法,在DFSInputStream读取完第一个块的数据以后,会关闭与该DataNode的连接,转而去连接下一个距离最近且存着第二个块数据的DataNode节点,继续读取数据。整个过程对于Client而言是完全透明的,在客户端而言,它一直在读取一个连续的数据流

步骤六:Client在读取了文件前几个块的数据以后,根据需要,Client可能会询问NameNode节点检索下一批DataNode数据块的位置,然后继续通过DFSInputStream去读取数据。客户端一旦完成了数据读取,便会对FSDataInputStream调用close()方法。

读取流程的细节:

在读取过程中,如果某一个DataNode发生故障,DFSInputStream会尝试连接另外一个最临近的DataNode,并记下该故障DataNode,保证后面不会再去该节点读取数据。DFSInputStream会通过校验和去检查读取的文件的正确性。

这种读取流程,让NameNode的工作量大大减少,只需要响应客户端的块位置请求即可,无需响应数据请求,所以可以支持高扩展。

距离是按照带宽来进行计算的,一般来说可以依据场景,对带宽进行递减: 1.同一节点的不同进程 2.同一机架的不同节点 3.同一数据中心的不同机架上的节点 4.不同数据中心的节点

HDFS写流程

HDFS写流程图 (图片来源-慕课网)

  1. 首先,客户端会向NameNode请求写入文件

  2. NameNode通过查询自己维护的节点信息,向客户端返回还有空间可以存放数据的DataNode节点信息

  3. 客户端将data文件进行分块,然后将块内容以及NameNode发送给客户端的信息一起发送给DataNode-1

  4. DataNode-1收到以后,会在管线中进行块数据的备份复制,使得达到块数据的最小复本数

  5. 当DataNode-1-2-3都存储完数据块以后,将会反馈给NameNode存储完成数据块-1信息,NameNode更新一下元数据信息,接着NameNode再将该信息返回给客户端

  6. 客户端再次开始存储数据块-2

然而上述的流程还不够清晰,有很多细节值得去深入了解一下。比如: 客户端是如何将块文件写入到DataNode中的,是以块为单位直接传输吗? DataNode如何实现将数据备份到其他DataNode节点上的呢? 如果在写入块数据的过程中,发生了错误,HDFS会怎么处理呢?

来自《Hadoop权威指南》的HDFS写文件流程图:

 

客户端如何将文件写入到DataNode?

create:

  • 客户端通过DistributedFileSystem对象调用create()方法来创建一个新文件

  • DistributedFileSystem的create()方法会返回一个FSDataOutputStream对象

  • FSDataOutputStream封装了一个DFSOutputStream对象,在其构造函数中会使用RPC远程调用NameNode的create()方法来创建一个文件

  • NameNode会先对文件创建操作进行检查(比如该客户端有没有权限创建文件,该文件名是否已经存在?),若检查没问题,NameNode则为创建新文件做一条记录,否则创建失败,并向客户端返回IOException异常

write packet 和 ack packet:

客户端写入数据块时,DFSOutputStream将其分成一个一个的包(packet),将packet放到pipeline(管线)里进行写入。写入时,会使用两个队列,一个是“数据队列”用于存放要写入packet;一个则是“确认队列“,用于接收DataNode发来的确认Ack。

写入过程:(DataStreamer负责处理)

  • 一般会使用FSDataOutputStream的write方法

  • FSDataOutputStream的write方法会调用DFSOutputStream的write方法,而DFSOutputStream继承自FSOutputSummer,所以实际上是调用FSOutputSummer的write方法

  • 首先将package 1写入DataNode 1然后由DataNode 1负责将package 1写入DataNode 2,同时客户端可以将pacage 2写入DataNode 1

  • 然后DataNode 2负责将package 1写入DataNode 3, 同时客户端可以讲package 3写入DataNode 1,DataNode 1将package 2写入DataNode 2

  • 就这样将一个个package排着队的传递下去,直到所有的数据全部写入并复制完毕

确认过程:

  • DataStreamer线程负责把准备好的数据packet,顺序写入到DataNode,未确认写入成功的packet则移动到ackQueue,等待确认。

  • DataStreamer线程传输数据到DataNode时,要向namenode申请数据块,在NameNode分配了DataNode和block以后,createBlockOutputStream开始写入数据。

  • 只有对于一个数据包(packet)收到管道内的所有DataNode的ack之后,才能将该数据包从确认队列中删除。

当写入时有DataNode发生故障,导致数据无法正常写入,该怎么处理??

DataStreamer会启动ResponseProcessor线程,它负责接收datanode的ack

  • 首先将管线关闭

  • 将确认队列的数据包添加回数据队列的前端

  • 将发生故障的DataNode从管线中移除

  • 在另一个正常的DataNode节点对当前的数据块做一个标记,并将标识发给NameNode,使损坏的DataNode恢复正常后能够删除已存储的部分数据块

  • 通过RPC调用DataNode的recoverBlock方法来恢复数据块

  • 以剩下的DataNode节点建立新的管线,继续写入数据(NameNode注意到块复本数量不足时,会重新添加一个DataNode进行副本数据保存)

HDFS如何选择副本的存储位置?

副本存放位置的选定需要同时对可靠性、写入带宽和读取带宽同时均衡考量进行选取 默认布局:

  • 第一个副本一般放在客户端,如果客户端在数据中心之外,则会随机在数据中心选择一个节点

  • 第二个副本,选择和第一个在不同的机架的一个节点

  • 第三个副本则选择和第二个副本在一个机架上,但是是不同的节点

  • 后面的副本会随机选择,不过系统会尽量避免一个机架上会存放过多的副本

参考资料

posted @ 2019-08-25 11:16  XK-T  阅读(2598)  评论(0编辑  收藏  举报