HDFS恢复过程
2017-05-10 15:43:10 阿炯

HDFS恢复过程1

运行或者移动生产环境中的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。

原文地址,感谢混绅士的翻译。

HDFS恢复过程2

本篇主要接着上篇介绍Pipeline Recovery。

写Pipeline

当HDFS client执行一个写操作时,数据是以序列化的block形式写进去的。其中block又被分为很多packets,将packets发送到由一些dn组成的pipeline中,如下图所示:
pipeline流程


pipeline分为三个阶段:

    Pipeline setup
    client向pipeline发送一个Write_Block请求,pipeline中的最后一个DataNode往回发送一个ack。client收到ack之后,pipeline就处于setup状态,准备写入数据。
    
    Data streaming
    数据通过packets发送到pipeline中。client端缓存数据到buffer中,buffer满之后写入packet,等packet满之后,发送packet到pipeline中。如果client调用hflush(),即使当前packet没有被写满,也会发送到pipeline中,并且下一个packet在收到当前packet的ack之前不会发送到pipeline中(上图中的packet2就是调用hflush)。
    
    Close(改变replica的状态为finalize并且关闭pipeline)
    client等待收到所有packet的ack之后,才会发送一个关闭请求。pipeline中的dn改变相应replica的状态为FINALIZED,并向nn报告。当nn收到replica的FINALIZED状态的个数满足最小副本数时,nn改变block的状态为COMPLETE。

Pipeline Recovery

无论在pipeline三个阶段中的哪个阶段,只要当pipeline中的dn发生error时,Pipeline Recovery就会被启动。

Recovery from Pipeline Setup Failure

1、当pipeline用来新增加一个block时,client放弃这个block,并向nn请求一个新的block和一组新的dn组重新组成一个pipeline。
2、当pipeline用来追加一个block时,client使用剩余的dn重建pipeline并且增加block的generation stamp。

Recovery from Data Streaming Failure

1、当pipeline中的dn发现error(checksum异常或者写到磁盘发生异常),发生error的dn关闭TCP/IP连接,移出pipeline。如果数据没有损坏,将缓存中的数据写入相应的block和checksum文件中(If the data is deemed not corrupted, it also writes buffered data to the relevant block and checksum (METADATA) files.)。
2、当client发现异常,client停止向pipeline中发送数据,使用剩余正常的dn重建一个新的pipeline。此时,block的所有replica都有一个新的GS。
3、client带着新的GS发送数据packet。有些dn如果已经接受到这些数据,则忽略这些packet并向pipel的下游发送这些packet。

Recovery from Close Failure

当pipeline处于close状态时,client发现故障,则利用剩余正常的dn重建pipeline。每个dn递增block的GS,如果replica不是finalized状态就将其改为finalized。

当pipeline中的一个dn发生故障,则将其故障节点从pipeline中移除。在pipeline recovery阶段,client可能需要利用剩余正常的节点重建pipeline。(在重建pipeline中,是否需要新的dn替代发生故障的dn,这个决策依赖DataNode替换策略,在下面的章节中介绍)复制监控线程将会检查block的复本,使其满足配置的副本因子。

DataNode Replacement Policy upon Failure

在pipeline recovery中,决定是否增加dn替换发生故障的dn的策略有4种。如下:

    DISABLE: 禁止进行dn替换,并在服务器端抛出一个异常。类似于client端的NEVER。
    NEVER: 当pipeline失败时不替换dn。(一般不这样设置)
    DEFAULT: 基于以下规则替换dn:
    将配置的副本因子数设为r
    将pipeline中存活的dn数设为n
    如果r>=3并且满足下面任意一个条件才替换发生故障的dn
    floor(r/2) >= n; or (当hflush/append被调用并且r > n)
    ALWAYS: Always是只要存在dn发生故障就会有新的dn进行替换。如果不能替换则失败。

如果想禁止这些策略,可以设置下面这个配置(默认值是true):
dfs.client.block.write.replace-datanode-on-failure.enable

假如上面的配置是true,则默认的替换策略是DEFAULT。由下面的属性控制:
dfs.client.block.write.replace-datanode-on-failure.policy
当使用DEFAULT或者ALWAYS时,如果pipeline中只有一个dn替换成功了,pipeline recovery将不会成功,并且client将不能在执行写操作。这个问题通过设置下面的属性进行解决:
dfs.client.block.write.replace-datanode-on-failure.best-effort
此属性默认是false。如果是默认值,client会一直尝试替换发生故障的dn直到满足特定的策略。如果设置为true,即使特定的策略不满足(在pipeline中只有一个dn成功,比要求的数量要少),client依然可以继续写。

原文地址,感谢混绅士的翻译。