hadoop之hdfs架构详解

  • 2019 年 10 月 3 日
  • 筆記

本文主要从两个方面对hdfs进行阐述,第一就是hdfs的整个架构以及组成,第二就是hdfs文件的读写流程。

一、HDFS概述

     标题中提到hdfs(Hadoop Distribute File System)是分布式文件系统

     分布式文件系统 distributed file system 是指文件系统管理的物理存储资源不一定直接链接在本地节点上,而是通过计算机网络与节点相连,可让多机器上的多用户分享文件和存储空间。分布式文件系统的设计基于客户机/服务器模式

分布式文件系统的特点:
1、分布式文件系统可以有效解决数据的存储和管理难题
2、将固定于某个地点的某个文件系统,扩展到任意多个地点/多个文件系统
3、众多的节点组成一个文件系统网络
4、每个节点可以分布在不同的地点,通过网络进行节点间的通信和数据传输
5、在使用分布式文件系统时,无需关心数据是存储在哪个节点上、或者是从哪个节点获取的,只需要像使用本地文件系统一样管理和存储文件系统中的数据

Hadoop之(HDFS)是一种分布式文件系统,设计用于在商用硬件上运行。 它与现有的分布式文件系统有许多相似之处。 但是,与其他分布式文件系统的差异很大。
HDFS具有高度容错能力,旨在部署在低成本硬件上。
HDFS提供对应用程序数据的高吞吐量访问,适用于具有大型数据集的应用程序。
HDFS放宽了一些POSIX要求,以实现对文件系统数据的流式访问

HDFS优势:

1、可构建在廉价机器上,设备成本相对低  2、高容错性,HDFS将数据自动保存多个副本,副本丢失后,自动恢复,防止数据丢失或损坏  3、适合批处理,HDFS适合一次写入、多次查询(读取)的情况,适合在已有的数据进行多次分析,稳定性好  4、适合存储大文件,其中的大表示可以存储单个大文件,因为是分块存储,以及表示存储大量的数据

HDFS劣势:

1、由于提高吞吐量,降低实时性  2、由于每个文件都会在namenode中记录元数据,如果存储了大量的小文件,会对namenode造成很大的压力
3、不合适小文件处理,在mapreduce的过程中小文件的数量会造成map数量的增大,导致资源被占用,而且速度慢。 4、不适合文件的修改,文件只能追加在文件的末尾,不支持任意位置修改,不支持多个写入者操作

 

二、HDFS架构

hdfs架构图如下图所示:

 

 

HDFS具有主/从架构。HDFS集群由单个NameNode,和多个datanode构成。

NameNode:管理文件系统命名空间的主服务器和管理客户端对文件的访问组成,如打开,关闭和重命名文件和目录。负责管理文件目录、文件和block的对应关系以及block和datanode的对应关系,维护目录树,接管用户的请求。如下图所示:

 

 

1、将文件的元数据保存在一个文件目录树中
2、在磁盘上保存为:fsimage 和 edits
3、保存datanode的数据信息的文件,在系统启动的时候读入内存。

DataNode:(数据节点)管理连接到它们运行的​​节点的存储,负责处理来自文件系统客户端的读写请求。DataNodes还执行块创建,删除

Client:(客户端)代表用户通过与nameNode和datanode交互来访问整个文件系统,HDFS对外开放文件命名空间并允许用户数据以文件形式存储。用户通过客户端(Client)与HDFS进行通讯交互。

块和复制:
我们都知道linux操作系统中的磁盘的块的大小默认是512,而hadoop2.x版本中的块的大小默认为128M,那为什么hdfs中的存储块要设计这么大呢?
其目的是为了减小寻址的开销。只要块足够大,磁盘传输数据的时间必定会明显大于这个块的寻址时间。

那为什么要以块的形式存储文件,而不是整个文件呢?
1、因为一个文件可以特别大,可以大于有个磁盘的容量,所以以块的形式存储,可以用来存储无论大小怎样的文件。
2、简化存储系统的设计。因为块是固定的大小,计算磁盘的存储能力就容易多了
3、以块的形式存储不需要全部存在一个磁盘上,可以分布在各个文件系统的磁盘上,有利于复制和容错,数据本地化计算

块和复本在hdfs架构中分布如下图所示:

 

 

     既然namenode管理着文件系统的命名空间,维护着文件系统树以及整颗树内的所有文件和目录,这些信息以文件的形式永远的保存在本地磁盘上,分别问命名空间镜像文件fsimage和编辑日志文件Edits。datanode是文件的工作节点,根据需要存储和检索数据块,并且定期的向namenode发送它们所存储的块的列表。那么就知道namenode是多么的重要,一旦那么namenode挂了,那整个分布式文件系统就不可以使用了,所以对于namenode的容错就显得尤为重要了,hadoop为此提供了两种容错机制

容错机制一:

       就是通过对那些组成文件系统的元数据持久化,分别问命名空间镜像文件fsimage(文件系统的目录树)和编辑日志文件Edits(针对文件系统做的修改操作记录)。磁盘上的映像FsImage就是一个Checkpoint,一个里程碑式的基准点、同步点,有了一个Checkpoint之后,NameNode在相当长的时间内只是对内存中的目录映像操作,同时也对磁盘上的Edits操作,直到关机。下次开机的时候,NameNode要从磁盘上装载目录映像FSImage,那其实就是老的Checkpoint,也许就是上次开机后所保存的映像,而自从上次开机后直到关机为止对于文件系统的所有改变都记录在Edits文件中;将记录在Edits中的操作重演于上一次的映像,就得到这一次的新的映像,将其写回磁盘就是新的Checkpoint(也就是fsImage)。但是这样有很大一个缺点,如果Edits很大呢,开机后生成原始映像的过程也会很长,所以对其进行改进:每当 Edits长到一定程度,或者每隔一定的时间,就做一次Checkpoint,但是这样就会给namenode造成很大的负荷,会影响系统的性能。于是就有了SecondaryNameNode的需要,这相当于NameNode的助理,专替NameNode做Checkpoint。当然,SecondaryNameNode的负载相比之下是偏轻的。所以如果为NameNode配上了热备份,就可以让热备份兼职,而无须再有专职的SecondaryNameNode。所以架构图如下图所示:

 

SecondaryNameNode工作原理图:

 

 

SecondaryNameNode主要负责下载NameNode中的fsImage文件和Edits文件,并合并生成新的fsImage文件,并推送给NameNode,工作原理如下:

1、secondarynamenode请求主namenode停止使用edits文件,暂时将新的写操作记录到一个新的文件中;
2、secondarynamenode从主namenode获取fsimage和edits文件(通过http get)
3、secondarynamenode将fsimage文件载入内存,逐一执行edits文件中的操作,创建新的fsimage文件。
4、secondarynamenode将新的fsimage文件发送回主namenode(使用http post).
5、namenode用从secondarynamenode接收的fsimage文件替换旧的fsimage文件;用步骤1所产生的edits文件替换旧的edits文件。同时,还更新fstime文件来记录检查点执行时间。
6、最终,主namenode拥有最新的fsimage文件和一个更小的edits文件。当namenode处在安全模式时,管理员也可调用hadoop dfsadmin –saveNameSpace命令来创建检查点。

       从上面的过程中我们清晰的看到secondarynamenode和主namenode拥有相近内存需求的原因(因为secondarynamenode也把fsimage文件载入内存)。因此,在大型集群中,secondarynamenode需要运行在一台专用机器上。

      创建检查点的触发条件受两个配置参数控制。通常情况下,secondarynamenode每隔一小时(有fs.checkpoint.period属性设置)创建检查点;此外,当编辑日志的大小达到64MB(有fs.checkpoint.size属性设置)时,也会创建检查点。系统每隔五分钟检查一次编辑日志的大小。

容错机制二:

高可用方案(详情见:hadoop高可用安装和原理详解

 

三、HDFS读数据流程

HDFS读数据流程如下图所示:

 

 

1、客户端通过FileSystem对象(DistributedFileSystem)的open()方法来打开希望读取的文件。

2、DistributedFileSystem通过远程调用(RPC)来调用namenode,获取到每个文件的起止位置。对于每一个块,namenode返回该块副本的datanode。这些datanode会根据它们与客户端的距离(集群的网络拓扑结构)排序,如果客户端本身就是其中的一个datanode,那么就会在该datanode上读取数据。DistributedFileSystem远程调用后返回一个FSDataInputStream(支持文件定位的输入流)对象给客户端以便于读取数据,然后FSDataInputStream封装一个DFSInputStream对象。该对象管理datanode和namenode的IO。

3、客户端对这个输入流调用read()方法,存储着文件起始几个块的datanode地址的DFSInputStream随即连接距离最近的文件中第一个块所在的datanode,通过数据流反复调用read()方法,可以将数据从datanode传送到客户端。当读完这个块时,DFSInputStream关闭与该datanode的连接,然后寻址下一个位置最佳的datanode。

     客户端从流中读取数据时,块是按照打开DFSInputStream与datanode新建连接的顺序读取的。它也需要询问namenode来检索下一批所需块的datanode的位置。一旦客户端完成读取,就对FSDataInputStream调用close()方法。

   注意:在读取数据的时候,如果DFSInputStream在与datanode通讯时遇到错误,它便会尝试从这个块的另外一个临近datanode读取数据。他也会记住那个故障datanode,以保证以后不会反复读取该节点上后续的块。DFSInputStream也会通过校验和确认从datanode发送来的数据是否完整。如果发现一个损坏的块, DFSInputStream就会在试图从其他datanode读取一个块的复本之前通知namenode。

   总结:在这个设计中,namenode会告知客户端每个块中最佳的datanode,并让客户端直接联系该datanode且检索数据。由于数据流分散在该集群中的所有datanode,所以这种设计会使HDFS可扩展到大量的并发客户端。同时,namenode仅需要响应位置的请求(这些信息存储在内存中,非常高效),而无需响应数据请求,否则随着客户端数量的增长,namenode很快会成为一个瓶颈。

 

四、HDFS写数据流程

HDFS写数据流程图如下图所示:

 

 

1、首先客户端通过DistributedFileSystem上的create()方法指明一个预创建的文件的文件名

2、DistributedFileSystem再通过RPC调用向NameNode申请创建一个新文件(这时该文件还没有分配相应的block)。namenode检查是否有同名文件存在以及用户是否有相应的创建权限,如果检查通过,namenode会为该文件创建一个新的记录,否则的话文件创建失败,客户端得到一个IOException异常。DistributedFileSystem返回一个FSDataOutputStream以供客户端写入数据,与FSDataInputStream类似,FSDataOutputStream封装了一个DFSOutputStream用于处理namenode与datanode之间的通信。

3、当客户端开始写数据时(,DFSOutputStream把写入的数据分成包(packet), 放入一个中间队列——数据队列(data queue)中去。DataStreamer从数据队列中取数据,同时向namenode申请一个新的block来存放它已经取得的数据。namenode选择一系列合适的datanode(个数由文件的replica数决定)构成一个管道线(pipeline),这里我们假设replica为3,所以管道线中就有三个datanode。

4、DataSteamer把数据流式的写入到管道线中的第一个datanode中,第一个datanode再把接收到的数据转到第二个datanode中,以此类推。

5、DFSOutputStream同时也维护着另一个中间队列——确认队列(ack queue),确认队列中的包只有在得到管道线中所有的datanode的确认以后才会被移出确认队列

如果某个datanode在写数据的时候当掉了,下面这些对用户透明的步骤会被执行:

    管道线关闭,所有确认队列上的数据会被挪到数据队列的首部重新发送,这样可以确保管道线中当掉的datanode下流的datanode不会因为当掉的datanode而丢失数据包。

    在还在正常运行的datanode上的当前block上做一个标志,这样当当掉的datanode重新启动以后namenode就会知道该datanode上哪个block是刚才当机时残留下的局部损坏block,从而可以把它删掉。

    已经当掉的datanode从管道线中被移除,未写完的block的其他数据继续被写入到其他两个还在正常运行的datanode中去,namenode知道这个block还处在under-replicated状态(也即备份数不足的状态)下,然后他会安排一个新的replica从而达到要求的备份数,后续的block写入方法同前面正常时候一样。有可能管道线中的多个datanode当掉(虽然不太经常发生),但只要dfs.replication.min(默认为1)个replica被创建,我们就认为该创建成功了。剩余的replica会在以后异步创建以达到指定的replica数。

6、当客户端完成写数据后,它会调用close()方法。这个操作会冲洗(flush)所有剩下的package到pipeline中。

7、等待这些package确认成功,然后通知namenode写入文件成功。这时候namenode就知道该文件由哪些block组成(因为DataStreamer向namenode请求分配新block,namenode当然会知道它分配过哪些blcok给给定文件),它会等待最少的replica数被创建,然后成功返回。

注意:hdfs在写入的过程中,有一点与hdfs读取的时候非常相似,就是:DataStreamer在写入数据的时候,每写完一个datanode的数据块,都会重新向nameNode申请合适的datanode列表。这是为了保证系统中datanode数据存储的均衡性。

hdfs写入过程中,datanode管线的确认应答包并不是每写完一个datanode,就返回一个确认应答,而是一直写入,直到最后一个datanode写入完毕后,统一返回应答包。如果中间的一个datanode出现故障,那么返回的应答就是前面完好的datanode确认应答,和故障datanode的故障异常。这样我们也就可以理解,在写入数据的过程中,为什么数据包的校验是在最后一个datanode完成。

更多hadoop生态文章见: hadoop生态系列

参考:

《Hadoop权威指南 大数据的存储与分析 第四版》

https://hadoop.apache.org/docs/r2.7.7/hadoop-project-dist/hadoop-hdfs/HdfsUserGuide.html