HDFS追本溯源:租约,读写过程的容错处理及NN的主要数据结构

1.      Lease 的机制:

hdfs支持write-once-read-many,也就是说不支持并行写。那么对读写的相互排斥同步就是靠Lease实现的。Lease说白了就是一个有时间约束的锁。

client写文件时须要先申请一个Lease。相应到namenode中的LeaseManager。client的client name就作为一个lease的holder,即租约持有者。LeaseManager维护了文件的path与lease的相应关系,还有clientname->lease的相应关系。LeaseManager中有两个时间限制:softLimitand hardLimit。

软限制就是写文件时规定的租约超时时间,硬限制则是考虑到文件close时未来得及释放lease的情况强制回收租约。

LeaseManager中另一个Monitor线程来检測Lease是否超过hardLimit。而软租约的超时检測则在DFSClient的LeaseChecker中进行。

当client(DFSClient)create一个文件的时候。会通过RPC 调用 namenode 的createFile方法来创建文件。进而又调用FSNameSystem的startFile方法,又调用 LeaseManager 的addLease方法为新创建的文件加入一个lease。

假设lease已存在,则更新该lease的lastUpdate (近期更新时间)值,并将该文件的path相应该lease上。之后DFSClient 将该文件的path 加入 LeaseChecker中。文件创建成功后,守护线程LeaseChecker会每隔一定时间间隔renew该DFSClient所拥有的lease。

LeaseManagement是HDFS中的一个同步机制,用于保证同一时刻仅仅有一个client对一个文件进行写或创建操作。如当 新建一个文件f时,client向NameNode发起一个create请求。那么leaseManager会想该client分配一个f文件的 lease。

client凭借该lease完毕文件的创建操作。

此时其它client无法获得f的当client长时间(默觉得超过1min)不进行操作 时,发放的lease将被收回。

LeaseManager主要完毕两部分工作:

  1. 文件create,write,complete操作时,创建lease、更新时间戳、回收lease
  2. 一个后台线程定期检查是否有过期的lease

LeaseManager的代码结构例如以下

当中Lease表示一个租约,包含一个client(holder)所拥有的全部文件锁(paths)。

Monitor是检查是否有过期租约的线程。

LeaseManager中有几个主要数据结构:

  1. leases(TreeMap<String, Lease>):维护holder -> leased的映射集合
  2. sortedLeases (TreeSet): lease集合
  3. sortedLeaseByPath(TreeMap<String, Lease>): 维护paths->lease的映射集合

一、创建lease

当client向NameNode发起create操作时。NameNode.create()调用FSNameSystem.startFile()->FSNameSystem.startFileInternal(),该方法终于会调用 leaseManager.addLease(cons.clientName, src)来创建lease。

LeaseRecovery ——租约回收

leaserecovery时机

lease发放之后,在不用时会被回收,回收的产经除上述Monitor线程检測lease过期是回收外,还有:

  1. NameNode收到DataNode的Sync block command时
  2. DFSClient主动关闭一个流时
  3. 创建文件时,假设该DFSClient的lease超过soft limit时

lease recovery 算法

1) NameNode查找lease信息

2) 对于lease中的每一个文件f。令b为f的最后一个block,作例如以下操作:

2.1) 获取b所在的datanode列表

2.2) 令当中一个datanode作为primarydatanode p

2.3) p 从NameNode获取最新的时间戳

2.4) p 从每一个DataNode获取block信息

2.5) p 计算最小的block长度

2.6) p 用最小的block长度和最新的时间戳来更新具有有效时间戳的datanode

2.7) p 通知NameNode更新结果

2.8) NameNode更新BlockInfo

2.9) NameNode从lease中删除f,假设此时该lease中全部文件都已被删除,将删除该lease

2.10) Name提交改动的EditLog

 

Client续约 —— DFSClient.LeaseChecker

在NameNode上的LeaseManager.Monitor线程负责检查过期的lease,那么client为了防止尚在使用的lease过期,须要定期想NameNode发起续约请求。该任务有DFSClient中的LeaseChecker完毕。

LeaseChecker结构例如以下:

当中pendingCreates是一个TreeMap<String, OutputStream>用来维护src->当前正在写入的文件的DFSOutputStream的映射。

其核心是周期性(每一个1s)调用run()方法来对租约过半的lease进行续约

NameNode接收到renewLease请求后,调用FSNameSystem.renewLease()并终于调用LeaseManager.renewLease()完毕续约。

 

2.      机架感知

HDFS机架感知

        通常,大型 Hadoop 集群是以机架的形式来组织的,同一个机架上不同 节点间的网络状况比不同机架之间的更为理想。

 另外, NameNode 设法将 数据块副本保存在不同的机架上以提高容错性。

         而 HDFS 不能够自己主动推断集群中各个 datanode 的网络拓扑情况 Hadoop允 许集群的管理员通过配置 dfs.network.script 參数来确定节点所处的机架。 文件提供了 IP->rackid 的翻译。 NameNode 通过这个得到集群中各个 datanode 机器的 rackid 。

 假设 topology.script.file.name 没有设定,则每一个 IP 都会翻译 成/ default-rack 。

  

        有了机架感知。 NameNode 就能够画出上图所看到的的 datanode 网络拓扑图。D1,R1 都是交换机,最底层是 datanode 。

 则 H1 的 rackid=/D1/R1/H1 。 H1的 parent 是 R1 。 R1 的是 D1 。 这些 rackid 信息能够通过topology.script.file.name 配置。

有了这些 rackid 信息就能够计算出随意两台datanode 之间的距离。

 

distance(/D1/R1/H1,/D1/R1/H1)=0  相同的 datanode

distance(/D1/R1/H1,/D1/R1/H2)=2  同一 rack 下的不同 datanode

distance(/D1/R1/H1,/D1/R1/H4)=4  同一 IDC 下的不同 datanode

distance(/D1/R1/H1,/D2/R3/H7)=6   不同 IDC 下的 datanode

 

3.     HDFS 文件删除恢复机制

 

           当用户或应用程序删除某个文件时,这个文件并没有立马从 HDFS 中删除。实际上, HDFS 会将这个文件重命名转移到 /trash 文件夹。

仅仅要文件还在/trash 文件夹中,该文件就能够被迅速地恢复。文件在 /trash 中保存的时间是可 配置的。当超过这个时间时, Namenode 就会将该文件从名字空间中删除。 删除文件会使得该文件相关的数据块被释放。

注意。从用户删除文件到 HDFS 空暇空间的添加之间会有一定时间的延迟。

            仅仅要被删除的文件还在 /trash 文件夹中,用户就能够恢复这个文件。

假设 用户想恢复被删除的文件,他 / 她能够浏览 /trash 文件夹找回该文件。 /trash 目 录仅仅保存被删除文件的最后副本。 /trash 文件夹与其它的文件夹没有什么差别 ,除了一点:在该文件夹上 HDFS 会应用一个特殊策略来自己主动删除文件。眼下 的默认策略是删除 /trash 中保留时间超过 6 小时的文件。将来,这个策略能够 通过一个被良好定义的接口配置。

 

开启回收站

Hdfs -site.xml

<configuration>

      <property>

              <name>fs.trash.interval</name>

               <value> 1440 </value>

               <description>Number ofminutes betweentrash checkpoints.

                       If zero,the trashfeature is disabled.

               </description>

      </property>

</configuration>

1, fs.trash.interval 參数设置保留时间为 1440 秒 (1 天 )

2,   回收站的位置:在 HDFS 上的  / user/$USER/.Trash/Current/

 

4.     数据完整性

            从某个 Datanode 获取的数据块有可能是损坏的,损坏可能是由Datanode 的存储设备错误、网络错误或者软件 bug 造成的。 HDFS client软 件实现了对 HDFS 文件内容的校验和 (checksum) 检查。当client创建一个新 的HDFS 文件,会计算这个文件每一个数据块的校验和。并将校验和作为一个 单独的隐藏文件保存在同一个 HDFS 名字空间下。

当client获取文件内容后 。它会检验从Datanode 获取的数据跟相应的校验和文件里的校验和是否匹 配,假设不匹配,client能够选择从其它 Datanode 获取该数据块的副本。

5.     改动副本数

1.集群仅仅有三个Datanode,hadoop系统replication=4时,会出现什么情况?

       对于上传文件到hdfs上时,当时hadoop的副本系数是几,这个文件的块数副本数就会有几份,不管以后你怎么更改系统副本系统,这个文件的副本数都不 会改变,也就说上传到分布式系统上的文件副本数由当时的系统副本数决定,不会受replication的更改而变化。除非用命令来更改文件的副本数。

由于 dfs.replication实质上是client參数,在create文件时能够指定详细replication,属性dfs.replication是不指定详细replication时的採用默认备份数。文件上传后,备份数已定。改动dfs.replication是 不会影响曾经的文件的。也不会影响后面指定备份数的文件。仅仅影响后面採用默认备份数的文件。

但能够利用hadoop提供的命令后期改某文件的备份 数:hadoop fs-setrep -R 1。假设你是在hdfs-site.xml设置了dfs.replication,这并一定就得了。由于你可能没把conf文件夹加入到你的project的classpath里。你的程序运行时取的dfs.replication可能是hdfs-default.xml里的dfs.replication,默认是3。可能这个就是造成你为什么dfs.replication老是3的原因。你能够试试在创建文件时。显式设定 replication。replication一般到3就能够了,大了意义也不大。

6.     HDFS的安全模式

          Namenode 启动后会进入一个称为安全模式的特殊状态。

处于安全模式 的Namenode 是不会进行数据块的复制的。 Namenode 从全部的 Datanode 接收心跳信号和块状态报告。

块状态报告包含了某个 Datanode 全部的数据 块列表。每一个数据块都有一个指定的最小副本数。当 Namenode 检測确认某 个数据块的副本数目达到这个最小值,那么该数据块就会被觉得是副本安全 (safely replicated) 的。在一定百分比(这个參数可配置)的数据块被 Namenode 检測确认是安全之后(加上一个额外的 30 秒等待时间), Namenode 将退出安全模式状态。接下来它会确定还有哪些数据块的副本没 有达到指定数目。并将这些数据块拷贝到其它 Datanode上。

7.      读过程分析

•使用HDFS提供的client开发库Client。向远程的Namenode发起RPC请求。

• Namenode会视情况返回文件的部分或者全部block列表,对于每一个block,Namenode都会返回有该block拷贝的DataNode地址;

•client开发库Client会选取离client最接近的DataNode来读取block。假设client本身就是DataNode,那么将从本地直接获取数据.

•读取完当前block的数据后,关闭与当前的DataNode连接,并为读取下一个block寻找最佳的DataNode。

•当读完列表的block后,且文件读取还没有结束,client开发库会继续向Namenode获取下一批的block列表。

•读取完一个block都会进行checksum验证。假设读取datanode时出现错误,client会通知Namenode,然后再从下一个拥有该block拷贝的datanode继续读。

8.     写过程流程分析

•使用HDFS提供的client开发库Client。向远程的Namenode发起RPC请求。

•Namenode会检查要创建的文件是否已经存在,创建者是否有权限进行操作,成功则会为文件 创建一个记录,否则会让client抛出异常。

•当client開始写入文件的时候,会将文件切分成多个packets。并在内部以数据队列"data queue"的形式管理这些packets。并向Namenode申请新的blocks。获取用来存储replicas的合适的datanodes列表, 列表的大小依据在Namenode中对replication的设置而定。

•開始以pipeline(管道)的形式将packet写入全部的replicas中。把packet以流的方式写入第一个datanode, 该datanode把该packet存储之后,再将其传递给在此pipeline中的下一个datanode,直到最后一个datanode。这样的写数据 的方式呈流水线的形式。

•最后一个datanode成功存储之后会返回一个ack packet。在pipeline里传递至client。在client的开发库内部维护着"ack queue",成功收到datanode返回的ackpacket后会从"ackqueue"移除相应的packet。

•假设传输过程中,有某个datanode出现了故障,那么当前的pipeline会被关闭,出现问题的datanode会从当前的pipeline中移除。剩余的block会继续剩下的datanode中继续以pipeline的形式传输。同一时候Namenode会分配一个新的datanode,保持replicas设定的数量。

 

 

流水线复制

              当client向 HDFS 文件写入数据的时候,一開始是写到本地暂时文件里。假设该文件的副 本系数设置为 3 ,当本地暂时文件累积到一个数据块的大小时,client会从 Namenode 获取一个 Datanode 列表用于存放副本。然后client開始向第一个 Datanode 数据传输。第一个 Datanode 一小部分一小部分 (4 KB) 地接收数据,将每一部分写入本地仓库。并同一时候传输该部分到列表中 第二个 Datanode节点。第二个 Datanode 也是这样,一小部分一小部分地接收数据。写入本地 仓库,并同一时候传给第三个 Datanode 。

最后。第三个 Datanode 接收数据并存储在本地。因此。 Datanode 能流水线式地从前一个节点接收数据,并在同一时候转发给下一个节点。数据以流水线的 方式从前一个 Datanode 拷贝到下一个

 

 

更细节的原理

          client创建文件的请求其实并没有马上发送给 Namenode ,其实,在刚開始阶 段 HDFS client会先将文件数据缓存到本地的一个暂时文件。应用程序的写操作被透 明地重定向到这个暂时文件。

当这个暂时文件累积的数据量超过一个数据块的大小 ,client才会联系 Namenode 。 Namenode 将文件名称插入文件系统的层次结构中,并 且分配一个数据块给它。

然后返回 Datanode 的标识符和目标数据块给client。

接着 client将这块数据从本地暂时文件上传到指定的 Datanode 上。当文件关闭时。在临 时文件里剩余的没有上传的数据也会传输到指定的 Datanode 上。然后client告诉 Namenode 文件已经关闭。此时 Namenode 才将文件创建操作提交到日志里进行存储 。

假设 Namenode 在文件关闭前宕机了,则该文件将丢失。

 

整个写流程例如以下:
        第一步,client调用DistributedFileSystem的create()方法。開始创建新文件:DistributedFileSystem创建DFSOutputStream,产生一个RPC调用,让NameNode在文件系统的命名空间中创建这一新文件。
        第二步,NameNode接收到用户的写文件的RPC请 求后,谁偶先要运行各种检查,如客户是否有相关的创佳权限和该文件是否已存在等。检查都通过后才会创建一个新文件,并将操作记录到编辑日志,然后DistributedFileSystem会将DFSOutputStream对象包装在FSDataOutStream实例中。返回client;否则文件 创建失败而且给client抛IOException。
        第三步,client開始写文 件:DFSOutputStream会将文件切割成packets数据包,然后将这些packets写到其内部的一个叫做dataqueue(数据队列)。dataqueue会向NameNode节点请求适合存储数据副本的DataNode节点的列表。然后这些DataNode之前生成一个Pipeline数据流管 道。我们假设副本集參数被设置为3,那么这个数据流管道中就有三个DataNode节点。
        第四步,首先DFSOutputStream会将packets向Pipeline数据流管道中的第一个DataNode节点写数据,第一个DataNode接收packets然后把packets写向Pipeline中的第二个节点,同理。第二个节点保存接收到的数据然后将数据写向Pipeline中的第三个DataNode节点。
        第五步,DFSOutputStream内部相同维护另 外一个内部的写数据确认队列——ackqueue。

当Pipeline中的第三个DataNode节点将packets成功保存后,该节点回向第二个DataNode返回一个确认数据写成功的 信息,第二个DataNode接收到该确认信息后在当前节点数据写成功后也会向Pipeline中第一个DataNode节点发送一个确认数据写成功的信 息,然后第一个节点在收到该信息后假设该节点的数据也写成功后。会将packets从ackqueue中将数据删除。
        在写数据的过程中,假设Pipeline数据流管道中的一个DataNode节点写失败了会发生什问题、须要做哪些内部处理呢?假设这样的情况发生。那么就会运行一些操作:
        首先,Pipeline数据流管道会被关闭,ack queue中的packets会被加入到dataqueue的前面以确保不会发生packets数据包的丢失。
        接着。在正常的DataNode节点上的以保存好的block的ID版本号会升级——这样发生问题的DataNode节点上的block数据会在节点恢复正常后被删除。失效节点也会被从Pipeline中删除。
        最后。剩下的数据会被写入到Pipeline数据流管道中的其它两个节点中。
        假设Pipeline中的多个节点在写数据是发生失败,那么仅仅要写成功的block的数量达到dfs.replication.min(默觉得1),那么就任务是写成功的,然后NameNode后通过一步的方式将block拷贝到其它节点。最后事数据副本达到dfs.replication參数配置的个数。
        第六步,,完毕写操作后,client调用close()关闭写操作,刷新数据。
        第七步,。在数据刷新完后NameNode后关闭写操作流。

到此,整个写操作完毕。

     

least recently used

9.      HDFS负载均衡

       HDFS的数据或许并非非常均匀的分布在各个DataNode中。一个常见的原因是在现有的集群上常常会增添新的DataNode节点。当新增一个 数据块(一个文件的数据被保存在一系列的块中)时,NameNode在选择DataNode接收这个数据块之前,会考虑到非常多因素。当中的一些考虑的是:

•将数据块的一个副本放在正在写这个数据块的节点上。

•尽量将数据块的不同副本分布在不同的机架上,这样集群可在全然失去某一机架的情况下还能存活。

•一个副本通常被放置在和写文件的节点同一机架的某个节点上,这样能够降低跨越机架的网络I/O。

•尽量均匀地将HDFS数据分布在集群的DataNode中。

10.  基本数据结构

FSNameSystem

FSNameSystem是HDFS文件系统实际运行的核心。提供各种增删改查文件操作接口。其内部维护多个数据结构之间的关系:

  1. fsname->block列表的映射
  2. 全部有效blocks集合
  3. block与其所属的datanodes之间的映射(该映射是通过block reports动态构建的。维护在namenode的内存中。每一个datanode在启动时向namenode报告其自身node上的block)
  4. 每一个datanode与其上的blocklist的映射
  5. 採用心跳检測依据LRU算法更新的机器(datanode)列表

FSDirectory

FSDirectory用于维护当前系统中的文件树。

其内部主要组成结构包含一个INodeDirectoryWithQuota作为根文件夹(rootDir)和一个FSImage来持久化文件树的改动操作。

INode

HDFS中文件树用相似VFS中INode的方式构建。整个HDFS中文件被表示为INodeFile。文件夹被表示为INodeDirectory。

INodeDiretoryWithQuota是INodeDirectory的扩展类,即带配额的文件文件夹

INodeFile表示INode书中的一个文件,扩展自INode,除了名字(name),父节点(parent)等之外,一个主要元素是blocks,一个BlockInfo数组。表示该文件相应的block信息。

BlocksMap

BlocksMap用于维护Block-> { INode, datanodes, self ref } 的映射 BlocksMap结构比較简单,实际上就是一个Block到BlockInfo的映射。

Block

Block是HDFS中的基本读写单元,主要包含:

  1. blockId: 一个long类型的块id
  2. numBytes: 块大小
  3. generationStamp: 块更新的时间戳

BlockInfo

BlockInfo扩展自Block,除基本信息外还包含一个inode引用。表示该block所属的文件。以及一个奇妙的三元组数组Object[] triplets,用来表示保存该block的datanode信息,假设系统中的备份数量为3。那么这个数组结构例如以下:

  1. DN1,DN2,DN3分别表示存有改block的三个datanode的引用(DataNodeDescriptor)
  2. DN1-prev-blk表示在DN1上block列表中当前block的前置block引用
  3. DN1-next-blk表示在DN1上block列表中当前block的后置block引用

DN2,DN3的prev-blk和next-blk相似。 HDFS採用这样的结构存放block->datanodelist的信息主要是为了节省内存空间,block->datanodelist之间的映射关系须要占用大量内存。假设相同还要将datanode->blockslist的信息保存在内存中,相同要占用大量内存。採用三元组这样的方式能够从当中一个block获得到改 block所属的datanode上的全部block列表。

FSImage

FSImage用于持久化文件树的变更以及系统启动时载入持久化数据。HDFS启动时通过FSImage来载入磁盘中原有的文件树。系统Standby之后。通过FSEditlog来保存在文件树上的改动,FSEditLog定期将保存的改动信息刷到FSImage中进行持久化存储。FSImage中文件元信息的存储结构例如以下(參见FImage.saveFSImage()方法)

FSImage头部信息
  1. layoutVersion(int):image layout版本号号,0.19版本号的hdfs中为-18
  2. namespaceId(int): 命名空间ID,系统初始化时生成。在一个namenode生命周期内保持不变,datanode想namenode注冊是返回改id作为 registerId,以后每次datanode与namenode通信时都携带该id,不认识的id的请求将被拒绝。

  3. numberItemOfTree(long): 系统中的文件总数
  4. generationTimeStamp: 生成image的时间戳

 參考资料:

1. http://blog.csdn.net/cklsoft/article/details/8917899

2. http://www.iteye.com/topic/1126509

3. http://jiangbo.me/blog/2012/10/18/hdfs-namenode-lease-management/

4. http://flyingdutchman.iteye.com/blog/1900536

原文地址:https://www.cnblogs.com/jhcelue/p/6783076.html