运行或者移动生产环境中的Hadoop时,对很好的掌握HDFS的恢复过程是非常重要的。
HDFS中一个重要的设计需求就是要保证在生产部署中持续正确的操作。尤其复杂的是在网络和节点故障的情况下保证写入HDFS的正确性,租约恢复、block恢复和pipeline恢复保证了写的正确性。理解这些恢复操作何时何地被调用,以及做了什么,能帮助用户或者开发者理解HDFS集群的原理。

在这篇博客中,你会对那些恢复流程有个更深入的理解。首先简单介绍下HDFS write pipeline和恢复流程,对block/replica的状态和generation stamps的概念进行解释,然后逐步介绍租约恢复和block恢复。

这系列文章被分为两篇:第一篇介绍租约恢复和block恢复的细节,第二篇主要介绍pipeline恢复。想了解更多的内容,请参考设计文档:Append, Hflush, and Read for implementation details

背景

HDFS中,文件被分为块存储。HDFS中的文件可以被多client同时读,但只能被一个client写入。block的多副本存储在不同的dn上保证了HDFS的容错需求。其中副本的个数被称为副本因子。当一个新文件的block被创建,或者打开一个已经存在的文件(因为写或者追加),HDFS写操作会创建一个由dns组成的pipeline来接收并存储副本(副本因子决定了在pipeline中dns的个数)。下图是block写入pipeline的流程:
block写入pipeline
client读文件时从存放文件block的dns中选择一个dn,并请求从dn上进行数据传输。

下面两个应用场景突显了容错的重要性:

  • HBase的Region Server(RS)写时会先写入WAL,WAL是一个HDFS文件,能够防止数据丢失。如果一个RS宕机,一个新的RS会启动并通过读取WAL文件去重构先前RS的状态。如果在RS宕机的时候,写入pipeline没有完成,在pipeline中的dns上的数据可能并没有同步。为了重构RS正确的状态,HDFS必须保证从WAL中读取数据的正确性。
  • Flume客户端需要实时的将数据写入HDFS中,甚至在pipeline中存在一些dn失败或者停止响应的情况下,也必须保证能够持续的写。

租约恢复、block恢复和pipeline恢复发生在以下的情况下:

  • 在client可以往HDFS上写文件之前,必须从nn上得到一个租约(Lease),这个租约也就相当于一个写锁。租约保证了只有一个client写的语义。如果当前client想要维持写操作,租约必须在预定义的时间周期内更新租约。如果一个租约没有被更新或者持有该租约的client死了,则租约会过期。当租约过期之后,HDFS会关闭租约对应的文件并且会释放代表这个client的租约,然后其它clients就能够写这个文件了。_这个过程被称为租约回收_。
  • 当租约恢复发生的时候,如果一个文件正在写入的最后一个block没有传播到在pipeline中的所有dn,写入到不同dn上的数据可能不同。在租约恢复导致这个文件被关闭之前,有必要保证最后一个block的所有replicas相同,_这个过程被称为block恢复_。_block恢复只有在租约恢复时被触发_,并且租约恢复只会触发一个文件的最后一个block(如果该block不是COMPLETE状态)进行block恢复。
  • 在写pipeline操作中,有些dn可能会失败。如果发生失败,底层的写操作不能被简单粗暴的失败。HDFS会尝试恢复这些error,允许client能够继续写入pipeline中。从pipeline中恢复error的流程被称为pipeline恢复。

下面的章节将对这些过程进行更加深入的介绍。

Blocks、Replicas和他们的状态

为了区分Namenode中的blocks和DataNode中的blocks,将NameNode中的blocks称为blocks,将DataNode中的blocks称为replicas。

DataNode中的replica有如下状态(定义在org.apache.hadoop.hdfs.server.common.HdfsServerConstants.java中):

  • FINALIZED: 当replica是这个状态时,该replica的写入操作已经完成,数据的大小不会发生变化,除非对该replica进行append操作。有一样generation stamp(GS)的block的所有replica是相同的。finalized replica的GS可能会在block恢复中递增。
  • RBW(Replica Being Written): 不管是新建一个文件还是重新打开一个文件进行追加,此时任何一个正在被写入的replica都RBW状态。RBW状态的replica总是文件的最后一个block。数据依然正在写入该replica,replica并没有finalized。_RBW replica中的数据(不一定是所有的数据)是可读的_。如果有任何故障发生,RBW状态的replica会尝试保存数据。
  • RWR(Replica Waiting to be Recovered): 如果dn宕机或者重启了,其上的所有RBW状态的replicas将会将状态转换为RWR。RWR状态下的replica将会变成过期的,然后被丢弃;或者参与租约恢复中。
  • RUR(Replica Under Recovery): 在租约恢复的过程中,任何一个非TEMPORARY状态的replica都有可能转换为RUR状态。
  • TEMPORARY: 在block复制(由replication monitor或者balancer引起的block复制操作)中会出现temporary状态的replica。此状态下的replica与RBW状态类似,只是该状态下的数据是不可见的。如果block复制失败,TEMPORARY状态下的replica会被删除。

block在NameNode中的状态(定义在org.apache.hadoop.hdfs.server.common.HdfsServerConstants.java)如下:

  • UNDER_CONSTRUCTION: block正在写入时处于此状态。在UNDER_CONSTRUCTION下的block是被打开文件的最后一个block,该block的长度和generation stamp都是可变的,并且_它的数据(不一定是所有的数据)是可见的_。nn保持写pipeline的跟踪(有效RBW replicas的位置),和对RWR replicas的定位
  • UNDER_RECOVERY: 如果一个文件的租约超期时,该文件的最后一个block是UNDER_CONSTRUCTION,在block恢复开始时,该block会变为UNDER_RECOVERY。
  • COMMITTED: COMMITTED意味着一个block的数据和generation stamp不会发生变化(除非再次打开进行追加),并且比上报FINALIZED状态、有着相同GS/length的replicas的最小副本数少(具体不太明白,可能翻译的有问题,附上原文–and there are fewer than the minimal-replication number of DataNodes that have reported FINALIZED replicas of same GS/length)。为了提供读服务,COMMITTED状态的block必须保持对RBW replicas位置的跟踪和FINALIZED replicas的GS、length的跟踪。当client请求nn增加一个block或者关闭文件时,处于UNDER_CONSTRUCTION状态的block将变为COMMITTED状态。如果一个文件的最后一个block或者倒数第二个block是COMMITTED状态,则该文件不能被关闭,client必须进行重试。
  • COMPLETE: 当发现了有着相同GS/length的FINALIZED状态下的replicas满足最小副本数,则该block由COMMITTED转为COMPLETE状态。只有当所有的block都变成COMPLETE时,该文件才能被关闭。即使一个block不满足最小副本数,也可能被强制变为COMPLETE,例如当先前的block还没有到COMPLETE状态,client请求一个新block,此时会将先前的block强制变为COMPLETE状态。

DataNodes将replica的状态持久化到磁盘,但是NameNode并不会讲block的状态持久化到磁盘。当NameNode重启时,nn会将先前打开文件的最后一个block的状态变为UNDER_CONSTRUCTION,将其余block的状态变为COMPLETE。

replica和block的状态转换图如下:
replica-state
block-state

Generation Stamp

对于每一个block来说,GS是一个单调递增的8-byte数字,由NameNode进行维护。block和replica的GS处于下面的目的被引入:

  • 检测一个block的陈旧的replica: 也就是说,这个replica的GS小于这个block的GS。这是可能发生的,例如一个追加操作,不知什么原因跳过了此replica,没有更新此replica,使该replica的GS依然是为追加之前的GS,而其余replica的GS现在已经是追加之后的GS。
  • 当一个DataNode死了一段时间,然后又重新加入了集群,此时在此DataNode上检测过时的replica。

当下面的情况发生时,一个新的GS会产生:

  • 创建一个新文件
  • client对一个已经存在的文件进行append或者truncate
  • client在向dns上写数据时发生错误,会请求一个新的GS
  • NameNode对一个文件发起租约恢复操作

Lease Recovery and Block Recovery

Lease Manager

租约在NameNode上被lease Manager进行管理。NameNode跟踪每个client打开的写文件。client没有必要为它打开的每个写文件进行单独更新租约,而是定期的向NameNode发送一个请求对所有的文件进行租约更新。

每个NameNode管理一个单独的HDFS namespace,每个HDFS namespace会有一个单独的lease manager来管理与该namespace相关的所有client租约。Federated HDFS集群可能有多个namespace,每个namespaces都有其自己的lease manger。

lease manager维护这两个超时时间(目前这两个超时时间是不可配置的),一个是softLimit(1m),另一个是hardLimit(1h)。lease manager管理的所有租约都遵守相同的softLimit和hardLimit。在softLimit过期之前,持有某文件租约的client独占该文件的写权限。如果softLimit超期并且client并没有更新租约或者关闭了文件,另一个client能强制接管这个租约。如果hardLimit超期并且client没有更新租约,HDFS假设此client已经退出,将自动关闭代表client的文件,从而恢复租约。

事实上client持有的某个文件租约并不会阻止其它client对此文件进行读,一个文件能同时有多个client进行读,但只能有一个client进行写。

lease manager内部支持的操作:

  • 为一个client增加一个租约和路径(如果这个client已经有了一个租约,则增加这个路径到这个租约。否则,创建一个新的租约,并添加路径到租约里面)
  • 移除client的租约和路径(如果这是租约的最后一个路径,则移除这个租约)
  • 检查soft/hard limit是否过期,
  • 和对给定的client进行renew租约。

lease manager有一个monitor线程,此线程周期性(每2s)的检查所有租约是否hardLimit超期,如果超期,则对租约中的所有文件触发租约恢复。

HDFS client通过org.apache.hadoop.hdfs.LeaseRenewer.LeaseRenewer类renews它自己的租约。LeaseRenewer对每个NameNode上的每个user运行一个线程去周期性的检查,当间隔超过租约检查的一半,则更新LeaseRenewer对应的所有client的租约。

(注意: 一个HDFS client只会关联一个NameNode; 请看它的构造器 org.apache.hadoop.hdfs.DFSClient)。 如果同一个应用想要访问联邦集群的不同NS的不同的文件,需要为每一个NameNode创建client。

Lease Recovery Process

租约的恢复过程被NameNode触发用来对给定的client进行恢复租约。在通过监控线程检查到达hardLimit的有效期,或者softLimit过期时,其他客户端尝试接管租约的情况下NameNode会触发Lease Recovery。Lease Recovery会检查相同client打开的每个写文件,如果这个文件的最后一个block不是COMPLETE状态则执行block recovery,并且关闭这个文件。Block Recovery只有在Lease Recovery时会被触发

下面是给定文件f的Lease Recovery算法。当client死了,该算法适用于该client打开的每个写文件。

1、得到包含f最后一个block的dns
2、从dns中分配一个dn作为primary dn p
3、p从NameNode中得到一个新的GS
4、p从每个dn上得到block的信息
5、p就是这个block的最小长度
6、p更新dns,拥有合法的GS,即新的GS和最小的block长度。
7、p确认NameNode更新的结果.
8、NameNode更新BlockInfo
9、NameNode移除f的租约(其他写的操作现在可以维护这个文件f的租约,进行写入操作)
10、NameNode提交这些改变到edit log.
第三步到第七步是算法的block recovery部分。如果一个文件需要block recovery,NameNode从拥有该文件最后一个block的replica的DataNodes中挑选一个primary DataNode,使其DataNode协调其余DataNodes进行block recovery。结束之后,这个DataNode想NameNode报告,NameNode更新这个block的内部状态,移除这个租约,最后想edit log提交改变。

有时管理员在hardLimit未超时之前,需要强制对某个文件进行Lease Recovery,此时可以使用一个CLI
hdfs debug recoverLease [-path <path>] [-retries <num-retries>]

总结

租约恢复,块恢复,和pipeline恢复对HDFS容错至关重要。他们一起保证了HDFS写的持久性和一致性,即使是在网络或者节点异常的情况下。

在下一篇中将介绍pipeline recovery。

原文地址