分布式文件系统HDFS架构解析

在“分布式系列之分开源分布式存储技术分析”中提到HDFS是以中间控制节点为代表的分布式存储架构,一部分节点 NameNode 是存放管理数据,另一部分节点DataNode存放业务数据。本文简要整理HDFS分布式文件系统的整体架构和关键技术,进一步了解不同分布式存储技术实现原因上的差异。


1、HDFS基本架构

HDFS是Hadoop核心项目的子项目,是分布式计算中数据存储管理的基础,具有高容错性、可扩展性,适合大数据量处理和流式批处理,可运行于廉价的服务器上。传统的HDFS架构采用master/slave架构,一个HDFS集群是由一个Namenode和一定数目的Datanodes组成。
在这里插入图片描述

  • NameNode是master,它是一个中心服务器,是这个集群的管理者,负责管理HDFS的命名空间(NameSpace)、配置副本策略和数据块(Block)映射信息,同时也会处理客户端读写请求。
  • DataNode是slave,NameNode下达命令,DataNode执行实际的操作。集群中的DataNode负责管理所在节点上的存储,主要负责存储实际的数据块和执行数据块的读/写操作。
  • Client负责与用户交互,同时可以执行以下操作
    • 文件切分,文件上传HDFS的时候,Client将文件切分成一个一个的Block,然后进行存储。
    • 与NameNode交互,获取文件的位置信息
    • 与DataNode交互,读取或者写入数据
    • Client提供一些命令来管理HDFS,比如启动或者关闭HDFS
    • Client可以通过一些命令来访问HDFS
  • Secondary NameNode:并非NameNode的热备。当NameNode挂掉的时候,它并不能马上替换NameNode并提供服务
    • 辅助NameNode,分担其工作量。
    • 定期合并fsimage和fsedits,并推送给NameNode。
    • 在紧急情况下,可辅助恢复NameNode

总之,HDFS暴露了文件系统的名字空间NameSpace,用户能够以文件的形式在上面存储数据。从内部看,一个文件其实被分成一个或多个数据块,这些块存储在一组Datanode上。Namenode执行文件系统的名字空间操作,比如打开、关闭、重命名文件或目录,同时也负责确定数据块到具体Datanode节点的映射。Datanode负责处理文件系统客户端的读写请求。在Namenode的统一调度下进行数据块的创建、删除和复制。

1.1 数据块Block

HDFS中的数据块Block是逻辑概念,默认块为64MB,block是HDFS分布式文件系统中的文件存储的逻辑单元。通常一个文件系统块为几千字节,磁盘块为512字节,HDFS的块比磁盘块大,其目的是为了最小化寻址开销。如果块设置得足够大,从磁盘传输数据的时间可以明显大于定位这个块开始位置所需的时间。这样,传输一个由多个块组成的文件的时间取决于磁盘传输速率。HDFS中使用逻辑的block有以下好处:

  1. 可以存储任意大的文件,不会受到任何单一节点的磁盘大小限制。HDFS可以将超大的文件分为众多小的逻辑块,分别存储在集群的各个服务器上。
  2. 使用抽象块作为操作的单元可以简化存储子系统,由于HDFS中块的大小是固定的,这样简化了存储系统的管理,特别是元数据信息可以和文件块内容分开存储,可以由其它系统管理元数据
  3. 数据块的使用利于分布式系统中复制容错的实现,HDFS中默认将文件块副本数设置为3,分别存储在集群的不同节点上。当一个块损坏时,系统会通过 NameNode获取元数据信息,在另外的机器上读取一个副本并进行存储。
1.2 NameNode工作原理
1.2.1 FsImage和Edits

在HDFS集群中,元数据放在内存中,在磁盘中备份元数据的FsImage,防止因为断电,造成元数据丢失。当在内存中的元数据更新时,同时更新FsImage的效率非常低,所以HDFS引入Edits文件。每当元数据有更新或者添加元数据时,修改内存中的元数据并追加到Edits中。这样,一旦NameNode节点断电,可以通过FsImage和Edits的合并,恢复元数据。为了避免长时间添加数据到Edits中,导致文件数据过大,效率降低的问题,HDFS会定期进行FsImage和Edits的合并,合并操作由NameNode完成。但是由于NameNode还有其他的任务,比如协调DataNode工作等,这会使NameNode的压力倍增,所以HDFS引入了Secondary Namenode这一角色,专门负责FsImage和Edits的合并。

1.2.2 NameNode工作流程

在这里插入图片描述

1)第一阶段:NameNode启动

  • 第一次启动NameNode格式化后,创建Fsimage和Edits文件。如果不是第一次启动,先滚动Edits并生成一个空的edits.inprogress,然后加载Edits和Fsimage到内存中,此时NameNode内存就持有最新的元数据信息
  • Client开始对NameNode发送元数据的增删改的请求,这些请求的操作首先会被记录到edits.inprogress中。如果此时NameNode挂掉,重启后会从Edits中读取元数据的信息
  • NameNode记录操作日志,更新滚动日志。
  • NameNode在内存中对元数据进行增删改。
  • 由于Edits中记录的操作会越来越多,Edits文件会越来越大,导致NameNode在启动加载Edits时会很慢,所以需要对Edits和Fsimage进行合并

2)第二阶段:Secondary NameNode工作

  • Secondary NameNode询问NameNode是否需要CheckPoint,触发CheckPoint需要满足两个条件中的任意一个,定时时间到和Edits中数据写满了。直接返回NameNode是否检查结果。
  • Secondary NameNode请求执行CheckPoint。
  • NameNode滚动Edits并生成一个空的edits.inprogress,滚动Edits的目的是给Edits打个标记,以后所有新的操作都写入edits.inprogress,其他未合并的Edits和Fsimage会拷贝到SecondaryNameNode的本地
  • 将滚动前的编辑日志和镜像文件拷贝到Secondary NameNode。
  • Secondary NameNode加载编辑日志和镜像文件到内存,并合并。
  • 生成新的镜像文件fsimage.chkpoint。
  • 拷贝fsimage.chkpoint到NameNode。
  • NameNode将fsimage.chkpoint重新命名成fsimage。
1.3 DataNode工作原理

在这里插入图片描述

一个数据块在DataNode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度、块数据的校验和以及时间戳。

  • DataNode启动后向NameNode注册,通过后,周期性(1小时)的向NameNode上报所有的块信息。
  • 心跳是每3秒一次,心跳返回结果带有NameNode给该DataNode的命令如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟没有收到某个DataNode的心跳,则认为该节点不可用。
  • 集群运行中可以安全加入和退出一些机器。
1.4 什么是NameSpace

HDFS支持传统的层次型文件组织结构。用户或者应用程序可以创建目录,然后将文件保存在这些目录里。Namenode 负责维护文件系统的名字空间,任何对文件系统名字空间或属性的修改都将被Namenode 记录下来。HDFS 会给客户端提供一个统一的目录树,客户端通过路径来访问文件,形如:hdfs://namenode:port/dir-a/dir-b/dir-c/file.data。

在这里插入图片描述

2、HDFS读写数据流程

2.1 HDFS写数据流程

在这里插入图片描述

HDFS的文件写入原理,主要包括以下几个步骤:

  1. 客户端通过调用 DistributedFileSystem 的create方法,创建一个新的文件。
  2. DistributedFileSystem通过RPC(远程过程调用)调用NameNode,去创建一个没有blocks关联的新文件。创建前,NameNode会做各种校验,比如文件是否存在,客户端有无权限去创建等。如果校验通过,NameNode就会记录下新文件,否则就会抛出IO异常。
  3. 前两步结束后会返回FSDataOutputStream的对象,和读文件的时候相似,FSDataOutputStream被封装成DFSOutputStream,DFSOutputStream可以协调NameNode和DataNode。客户端开始写数据到DFSOutputStream,DFSOutputStream会把数据切成一个个小packet,然后排成队列data queue。
  4. DataStreamer会去处理接受data queue,它先问询NameNode这个新的block最适合存储的在哪几个DataNode里,比如重复数是3,那么就找到3个最适合的DataNode,把它们排成一个pipeline。DataStreamer把packet按队列输出到管道的第一个DataNode中,第一个DataNode又把packet输出到第二个DataNode中,以此类推。
  5. DFSOutputStream还有一个队列叫ack queue,也是由packet组成,等待DataNode的收到响应,当pipeline中的所有DataNode都表示已经收到的时候,这时akc queue才会把对应的packet包移除掉。
  6. 客户端完成写数据后,调用close方法关闭写入流。
  7. DataStreamer把剩余的包都刷到pipeline里,然后等待ack信息,收到最后一个ack后,通知DataNode把文件标示为已完成。
2.2 HDFS读数据流程

在这里插入图片描述

HDFS的文件读取原理,主要包括以下几个步骤:

  1. 首先调用FileSystem对象的open方法,其实获取的是一个DistributedFileSystem的实例。
  2. DistributedFileSystem通过RPC(远程过程调用)获得文件的第一批block的locations,同一block按照重复数会返回多个locations,这些locations按照hadoop拓扑结构排序,距离客户端近的排在前面。
  3. 前两步会返回一个FSDataInputStream对象,该对象会被封装成 DFSInputStream对象,DFSInputStream可以方便的管理datanode和namenode数据流。客户端调用read方法,DFSInputStream就会找出离客户端最近的datanode并连接datanode。
  4. 数据从datanode源源不断的流向客户端。
  5. 如果第一个block块的数据读完了,就会关闭指向第一个block块的datanode连接,接着读取下一个block块。这些操作对客户端来说是透明的,从客户端的角度来看只是读一个持续不断的流。
  6. 如果第一批block都读完了,DFSInputStream就会去namenode拿下一批blocks的location,然后继续读,如果所有的block块都读完,这时就会关闭掉所有的流。

3、HDFS高可用架构

在Hadoop2.X之前,Namenode在HDFS集群中是单点的,每个HDFS集群只有一个namenode,一旦这个节点不可用,则整个HDFS集群将处于不可用状态。HDFS高可用(HA)方案就是为了解决上述问题而产生的,在HA HDFS集群中会同时运行两个Namenode,一个作为Active Namenode(Active),一个作为Standby Namenode。Standby Namenode的命名空间与Active Namenode是实时同步的,所以当Active Namenode发生故障而停止服务时,Standby Namenode可以立即切换为活动状态,从而不影响HDFS集群服务。

在这里插入图片描述

3.1 Host-Active Namenode状态同步

为了使Standby节点与Active节点的状态能够同步一致,两个节点都需要同一组独立运行的节点(JournalNodes,JNS)通信。当Active Namenode执行了修改命名空间的操作时,它会定期将执行的操作记录在editlog中,并写入JNS的多数节点中。而Standby Namenode会一直监听JNS上editlog的变化,如果发现editlog有改动,Standby Namenode就会读取editlog并与当前的命名空间合并。当发生了故障切换时,Standby节点会保证已经从JNS上读取了所有editlog并与命名空间合并,然后才会从Standby状态切换为Active状态。通过这种机制,保证了Active Namenode与Standby Namenode之间命名空间状态的一致性。

为了使故障切换能够很快的执行完毕,就要保证Standby节点也保存了实时的数据块的存储信息。这样在发生故障切换时,Standby节点就不需要等待所有的数据节点进行全量数据块同步,而直接可以切换到Active状态。为了实现这个机制,Datanode会同时向这两个Namenode发送心跳以及块状态信息。这样就实现了Active Namenode 和standby Namenode 的元数据就完全一致,一旦发生故障,就可以马上切换,也就是热备。

这里需要注意的是 Standby Namenode只会更新数据块的存储信息,并不会向namenode 发送复制或者删除数据块的指令,这些指令只能由Active namenode发送。

3.2 HA脑裂问题

如果Zookeeper客户端机器负载过高或者正在进行JVM Full GC,那么可能会导致Zookeeper客户端到服务端的心跳不能正常发出,一旦这个时间持续较长,超过了配置的Zookeeper Session Timeout参数的话, Zookeeper服务端就会认为客户端的session已经过期从而将客户端的Session关闭。“假死”有可能引起分布式系统常说的双主或脑裂(brain-split)现象。在HA架构中需要保证同一时刻只有一个处于Active状态的Namenode,否则就会出现两个Namenode同时修改命名空间的问题,也就是脑裂(Split-brain)。脑裂的HDFS集群很可能造成数据块的丢失,以及向Datanode下发错误的指令等异常情况。为了预防脑裂的情况,HDFS提供了三个级别的隔离机制(fencing):

  1. 共享存储隔离:同一时间只允许一个Namenode向JournalNodes写入editlog数据。
  2. 客户端隔离:同一时间只允许一个Namenode响应客户端的请求。
  3. Datanode隔离:同一时间只允许一个Namenode向Datanode下发名字节点指令,比如删除、复制数据块指令等等。
3.3 HA存储共享机制

HDFS的HA高可用架构中,Active NameNode和Standby NameNode之间需要共享editlog日志文件。Active Namenode会将日志文件写到共享存储上,Standby Namenode会实时的从共享存储读取edetlog文件,然后合并到Standby Namenode的命名空间中。这样一旦Active Namenode发生错误,Standby Namenode可以立即切换到Active状态。在Hadoop2.6中,提供了QJM(Quorum Journal Manager)方案来解决HA共享存储问题,Quorum Journa是一个基于paxos算法的HA设计方案。基于 QJM 的共享存储系统主要用于保存EditLog,并不保存 FSImage 文件。 FSImage 文件还是在 NameNode 的本地磁盘上。

在这里插入图片描述

Quorum Journal方案依赖于这样一个概念:HDFS集群中有2N+1个JN存储editlog文件,这些editlog文件是保存在JN的本地磁盘上的。每个JN对QJM暴露QJM接口QJournalProtocol,允许Namenode读写editlog文件。当Namenode向共享存储写入editlog文件时,它会通过QLM向集群中所有的JN发送写editlog文件请求,当有一半以上的JN返回写操作成功时,即认为写成功。

4、总结

本文主要介绍了HDFS的整体架构,NameNode和DataNode的工作原理,以及HDFS的读写流程,最后介绍HDFS架构演进下高可用的实现机制。


参考资料:

  1. https://blog.csdn.net/eagleuniversityeye/article/details/104068889
  2. https://hadoop.apache.org/docs/r1.0.4/cn/hdfs_design.html
  3. https://www.cnblogs.com/yangyquin/p/5017785.html
  4. https://blog.csdn.net/u012736748/article/details/79534019
  5. https://www.cnblogs.com/zsql/p/11587240.html
  6. https://www.cnblogs.com/codeOfLife/p/5375120.html

转载请注明原文地址:https://blog.csdn.net/solihawk/article/details/123981573
文章会同步在公众号“牧羊人的方向”更新,感兴趣的可以关注公众号,谢谢!
在这里插入图片描述


版权声明:本文为solihawk原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。