设为首页 加入收藏

TOP

HDFS追本溯源:租约,读写过程的容错处理及NN的主要数据结构
2019-04-22 00:17:32 】 浏览:41
Tags:HDFS 追本溯源 租约 读写 过程 容错 处理 主要 数据结构
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/anzhsoft2008/article/details/24776889

1.Lease 的机制:

hdfs支持write-once-read-many,也就是说不支持并行写,那么对读写的互斥同步就是靠Lease实现的。Lease说白了就是一个有时间约束的锁。客户端写文件时需要先申请一个Lease,对应到namenode中的LeaseManager,客户端的client name就作为一个lease的holder,即租约持有者。LeaseManager维护了文件的path与lease的对应关系,还有clientname->lease的对应关系。LeaseManager中有两个时间限制:softLimitand hardLimit。

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

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

当客户端(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客户端软件实现了对HDFS文件内容的校验和(checksum)检查。当客户端创建一个新的HDFS文件,会计算这个文件每个数据块的校验和,并将校验和作为一个单独的隐藏文件保存在同一个HDFS名字空间下。当客户端获取文件内容后,它会检验从Datanode获取的数据跟相应的校验和文件中的校验和是否匹配,如果不匹配,客户端可以选择从其他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,向远程的Namenode发起RPC请求;

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

客户端开发库Client会选取离客户端最接近的DataNode来读取block;如果客户端本身就是DataNode,那么将从本地直接获取数据.

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

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

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

8.写过程流程分析

使用HDFS提供的客户端开发库Client,向远程的Namenode发起RPC请求;

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

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

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

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

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

流水线复制

当客户端向HDFS文件写入数据的时候,一开始是写到本地临时文件中。假设该文件的副本系数设置为3,当本地临时文件累积到一个数据块的大小时,客户端会从Namenode获取一个Datanode列表用于存放副本。然后客户端开始向第一个Datanode传输数据,第一个Datanode一小部分一小部分(4 KB)地接收数据,将每一部分写入本地仓库,并同时传输该部分到列表中第二个Datanode节点。第二个Datanode也是这样,一小部分一小部分地接收数据,写入本地仓库,并同时传给第三个Datanode。最后,第三个Datanode接收数据并存储在本地。因此,Datanode能流水线式地从前一个节点接收数据,并在同时转发给下一个节点,数据以流水线的方式从前一个Datanode复制到下一个

更细节的原理

客户端创建文件的请求其实并没有立即发送给Namenode,事实上,在刚开始阶段HDFS客户端会先将文件数据缓存到本地的一个临时文件。应用程序的写操作被透明地重定向到这个临时文件。当这个临时文件累积的数据量超过一个数据块的大小,客户端才会联系Namenode。Namenode将文件名插入文件系统的层次结构中,并且分配一个数据块给它。然后返回Datanode的标识符和目标数据块给客户端。接着客户端将这块数据从本地临时文件上传到指定的Datanode上。当文件关闭时,在临时文件中剩余的没有上传的数据也会传输到指定的Datanode上。然后客户端告诉Namenode文件已经关闭。此时Namenode才将文件创建操作提交到日志里进行存储。如果Namenode在文件关闭前宕机了,则该文件将丢失。

整个写流程如下:
第一步,客户端调用DistributedFileSystem的create()方法,开始创建新文件:DistributedFileSystem创建DFSOutputStream,产生一个RPC调用,让NameNode在文件系统的命名空间中创建这一新文件;
第二步,NameNode接收到用户的写文件的RPC请 求后,谁偶先要执行各种检查,如客户是否有相关的创佳权限和该文件是否已存在等,检查都通过后才会创建一个新文件,并将操作记录到编辑日志,然后DistributedFileSystem会将DFSOutputStream对象包装在FSDataOutStream实例中,返回客户端;否则文件 创建失败并且给客户端抛IOException。
第三步,客户端开始写文 件: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参数配置的个数。
第六步,,完成写操作后,客户端调用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

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇hadoop中的hdfs数据块为什么这么大 下一篇HDFS不适合存储小文件,如果生成..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目