Hadoop源码分析笔记(八):HDFS主要流程

HDFS主要流程

        本节介绍5个典型的HDFS流程:客户端到名字节点的元数据操作、客户端读文件、客户端写文件、数据节点到名字节点的注册和心跳、以及第二名字节点的合并数据。这些流程充分体现了HDFS实体间IPC接口和流式接口的配合。
客户端到名字节点的文件与目录操作
        客户端有到名字节点的大量元数据的操作,如更改文件名、在给定目录下创建一个子目录等,这些操作一般只涉及客户端和名字节点的交互,通过远程接口ClientProtocol进行。
         以创建子目录为例:
         当客户端调用HDFS的FileSystem实例,也就是DistributedFileSysstem的mkdir()方法时,让名字节点执行具体的创建子目录操作:在目录树数据结构上对应位置创建新的目录节点,同时记录这个操作并持久化到日志中,方法执行后,mkdir()方法返回ture,结束创建过程。期间,客户端和名字节点都不需要和数据节点交互。
        以客户端删除HDFS文件为例:
        操作在名字节点执行完毕后,数据节点上存放文件的内容的数据块也必须删除。但是,名字节点执行delete()方法时,它只标记操作涉及的需要被删除的数据块(当然也会记录delete操作并持久化到日志),而不会主动联系保存这些数据块的数据节点,立即删除数据。当保存着这些数据块的数据节点向名字节点发送心跳时,在心跳的应答中名字节点会通过DatanodeCommon命令数据节点删除数据块,在删除操作完成后的一段时间以后,才会被真正删除;名字节点和数据节点间永远维持着简单的主从关系,名字节点不会向数据节点发起任何IPC调用,数据节点需要配合名字节点执行的操作,都是通过数据节点心跳应答中携带的DatanodeCommand返回。
客户端读文件
        在客户端读取文件的过程中,客户端通过FileSystem.open()打开文件,对应的HDFS具体的文件系统,DistributedFileSystem创建输出流FSDataInoutStream,返回给客户端,客户端使用这个输入流读取数据。FSDataInputStream需呀和具体的输入流结合,一起形成过滤器流向外提供服务。
        对于HDFS来说,具体的输入流为DFSInputStream。在DFSInputStream的构造函数中,输出流通过ClientProtocol.getBlockLocations()远程接口调用名字节点,以确定文件开始部分数据块的保存位置,对于文件中的每个块,名字节点返回保存着该副本的数据节点地址。当然,这些数据节点根据它们与客户端的距离(利用网络的拓扑信息),进行了简单的排序。
        客户端调用FSdataInputStrea.read()方法读取文件时,DFSInputStream对象会通过和数据节点的“读数据”流接口,和最近的数据节点建立联系。客户端反复调用read()方法,数据会通过数据节点和客户端连接上的数据包返回客户端。当达到块的末端时,DFSInputStream会关闭和数据节点间的联系,并通过getBlockLications()远程方法获得保存着下一个数据块的数据节点信息(严格说,在对象没有缓存该数据块的位置时,才会使用这个远程方法)。
        由客户端直接联系名字节点,检索数据存放位置,并由名字节点安排数据节点读取顺序,这样的设计还有一个好处是,能够将读取文件引起的数据传输,分散到集群的各个数据节点,HDFS可以支持大量的并发客户端。同时,名字节点只处理数据块定位请求,不提供数据,否则,随着客户端数量的增长,名字节点会迅速成为系统的瓶颈。
客户端写文件
        即使不考虑数据节点出错后的故障处理,文件写入也是HDFS中最复杂的流程。
        以创建一个新文件并向文件写入数据,然后关闭文件为例:
        客户端调用DistributedFileSystem的create()方法创建文件,这时,DistributedFileSystem创建DFSOuptStream,并由远程过程调用,让名字节点执行同名方法,在文件系统的命名空间中创建一个新文件。名字节点创建新文件时,需要执行各种各样的检查,并记录穿件操作到编辑日志edits中。远程方法调用结束后,DistributedFileSystem将该DFSOutputStream对象包裹在FSDataOutputStream实例中,返回给客户端。
        在客户端写入数据时,由于create()调用创建了一个空文件,所有DFSOutputStream实例首先需要向名字节点申请数据块,addBlock()方法成功执行后,返回一个LocatedBlock对象。该对象包含了新数据块的标识和版本号,同时LocatedBlock.locs提供了数据流管道的信息,通过上述信息,DFSOutputStream就可以和数据节点联系,通过写数据接口建立数据流管道。客户端写入FSDataOutputStream流中数据,被分成一个一个文件包,放入DFSOutputStream对象内部列队。该队列中的文件包最后打包成数据包,发往数据流管道,并按照前面讨论的方式,流经管道上的各个数据节点,并持久化。确定包逆流而上,从数据流管道依次发往客户端,当客户端收到应答时,它将对应的包从内部队列移除。
        DFSOutputStream在写完一个数据块后,数据流管道上的节点,会通过和名字节点的DatanodeProtocol远程接口的blockReceived()方法,向名字节点提交数据块。如果数据队列中还有等待输出的数据,DFSOutputStream对象需要再次调用addBlock()方法,为文件添加新的数据块。
        客户端完成数据的写入后,调用close()方法关闭流。关闭意味着客户端不会再往流中写入数据,所有,当DFSOutputStream数据列队中的文件包都收到应答后,就可以使用ClientProtocol.complete()方法通知名字节点关闭文件,完成一次正常的写文件流程。
       如果在文件数据写入期间,数据节点出现故障,如果文件包没有收到应答,会重新添加到DFSOutputStream的输出队列。当前正常工作的数据节点上的数据块会赋予一个新的版本号,并通知名字节点。当出现故障的数据节点从故障中恢复过来以后,这样只有部分数据的数据块会因为数据块版本号和名字节点保存的版本号不匹配而删除。
      另外,在数据块写入过程中,可能会出现对于一个数据节点出现出现故障的情况下,这时,只要数据流管道中的数据节点数满足配置项${dfs.replication.min}的值(默认值为1),就认为写操作是成功的。后续这个数据块会被复制,直到满足文件的副本系数的要求。
数据节点的启动和心跳
         正常启动数据节点或者为升级而启动数据节点,都会向名字节点发送远程调用versionRequest(),进行必要的版本检查。这里的版本检查,只涉及构建版本号,保证他们间的HDFS版本是一致的。正常启动的数据节点,在版本检查结束后,会接着发送远程调用register(),向名字节点注册。DatanodeProtocol.register()的主要工作是检查,通过检查确认该数据节点是名字节点管理集群的成员。也就是说,用户不恩给你将某一个集群的数据节点,直接注册到另一个集群的名字节点,这保证了整个系统的数据一致性。注册成功后,数据节点会将他管理的所有数据块信息,通过blockReport()方法上报到名字节点,帮助名字节点建立HDFS文件数据块到数据节点的影射关系。这一步操作完成后,数据节点才会正式提供服务。
        由于名字节点和数据节点间存在着主从关系,数据节点需要每隔一段时间发送心跳到名字节点,如果名字节点长时间接收不到数据节点的心跳,它会认为该数据节点已经失效。名字节点如果有一些需要数据节点配合的动作,则会通过方法sendHeartbeat()返回。该返回值是一个DatanodeCommond数组,它带来了一系列名字节点指令。继续上面提到的客户端删除HDFS文件的例子,操作在名字节点执行完毕后,被删除文件的数据块会被标记,如果保存这些数据块的数据节点向名字节点发送心跳,则在放回的DatanodeCommonde数组里,有对应的命名编号为DNA_INVALIDATE的名字节点指令。数据节点执行指令,删除数据块,释放存储空间。
        考虑到一个规模的HDFS集群,一个名字节点会管理上千个数据节点,数据节点和名字节点的大部分交互都是数据节点到名字节点的心跳,这样的设计是非常棒的。
第二名字节点合并元数据
        客户端对HDFS的文件系统目录树进行修改时,名字节点都会在编辑日志里写入记录,以保证系统出现故障后,能够根据这些日志进行恢复。日志会随时间不断地增长,意味着如果系统重启后,需要进行日志恢复的时间会很长。为了避免这种情况的发生,HDFS引入了检查点机制,命名空间镜像(fsimage)文件就是系统元数据的持久性检查点,和编辑日志不同,它不能再每次对系统元数据进行修改后都进行更新。在一个比较大的运营集群中,fsimage文件可以有GB的大小。如果名字节点重新启动,元数据可以通过从磁盘中读入命名空间镜像,恢复到某一个恢复点,然后再执行检查点后记录的编辑日志,进行重建。
        为了解决Hadoop文件系统的编辑日志不断增长的问题,Hadoop引入了第二名字节点,它的唯一工作就是辅助名字节点,合并fsimage和编辑日志。
       该过程由第二名字节点发起,首先通过调用远程方法NamenodeProtocol.getEditLogSize()获得名字节点上编辑日志的大小。如果日志很小,就不需要合并元数据镜像和编辑日志,否则继续通过该远程接口上的rollEditLog(),启动一次检查点过程。这时,名字节点需要创建一个新的编辑日志edit.new,后续对文件系统元数据的改动,都会记录到这个新的日志里。而原有的命名空间镜像和编辑日志,则由第二名字节点,通过前面分析的HTTP接口读取到本地,并在内存中进行合并。合并后的结果输出为fsimage.ckpt,然后第二名字节点再次发起HTTP请求,通知名字节点数据已经准备好。名字节点通过HTTP GET请求,下载fsimage.ckpt。名字节点下载新的命名空间镜像结束后,会用fsimage.ckpt覆盖原来的fsimage,形成新的命名空间镜像,同时将新的编辑日志edit.new改名为edit。

        

 版权申明:本文部分摘自【蔡斌、陈湘萍】所著【Hadoop技术内幕 深入解析Hadoop Common和HDFS架构设计与实现原理】一书,仅作为学习笔记,用于技术交流,其商业版权由原作者保留,推荐大家购买图书研究,转载请保留原作者,谢谢!
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值