分布式系统与一致性
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.3 HDFS的错误处理

如果在写入过程中出现任何错误,那么HDFS会处理各种错误(不同于GFS,GFS会给客户端返回失败信息,最终导致数据不一致),试图从错误中恢复(recovery),通过恢复过程保证数据的一致性。

错误可能来源于各个组件,如DN、NN、客户端等。下面通过介绍在这些组件中会出现哪些错误以及如何处理,来讲解HDFS的错误处理。

3.3.1 DN的错误

当DN发生错误时,DN自己可能会发现这个错误并进行处理,客户端也可能会发现这个错误并进行处理。

1.DN自己处理错误

如果DN自己检查到错误(如网络发送数据错误、网络接收数据错误、磁盘操作错误),则DN自己会停止建立pipeline,或者退出所在的pipeline,具体会执行以下动作:

● 给上游DN回复失败信息。

● 关闭本地文件(将所有缓存的数据写入文件中)。

● 关闭TCP连接。

无论是因为发生错误,还是因为机器维护的需要,DN都有可能会重新启动。DN重新启动后,状态为rbw的副本会被加载为rwr(replica waiting to be recovered)状态,表示这个副本要开始进行恢复。检查文件的CRC,把文件长度设置为满足CRC校验,不能通过CRC校验的内容将被丢弃。

2.客户端处理DN的错误

客户端将数据写入pipeline中,如果收到DN返回的错误信息,则不管是哪个DN上的哪个步骤出错,客户端都需要处理这个错误。HDFS将客户端处理这些错误的过程称为管道恢复(pipeline recovery)

pipeline在不同阶段的错误,客户端要进行不同的处理。比如在建立pipeline阶段:

● 如果create block出错,则客户端会放弃这个block,要求NN再分配一个block。

● 如果append block出错,则客户端会为剩下没有出错的DN重新建立新的pipeline,并且向NN要求一个新的代戳,NN会增加代戳。

在输送数据阶段,按照下面的步骤处理。

● 客户端停止数据写入。

● 使用剩余的DN重新建立pipeline,并且向NN要求一个新的代戳,NN会增加代戳。

● 客户端使用新代戳向新pipeline中写入数据。

3.3.2 NN的错误

NN发生错误或者主动进行维护,可能会使NN重新启动。NN不会持久化存储block的状态,block的状态仅会被保存在内存中,NN在处理DN的定期上报信息(见3.2.5节)或客户端的上报信息(见3.2.3节)时,会更新内存中block的状态。重新启动后NN进入安全模式,在安全模式下,它会在内存中重建block的状态。

● 所有状态未关闭的文件的最后一个block都会被加载为UnderConstruction状态,其他的block会被加载为Complete状态(HDFS保证文件是顺序写入的,并且只有当前的block写满之后才会开始一个新的block,所以除了最后一个block,其他的block都应该是Complete状态)。

● NN会等待DN上报信息,直到至少每个block都收到一个副本的上报信息,并且符合以下条件,则退出安全模式。

■ 如果block的状态被标记为Complete,那么至少收到一个状态为finalized的副本上报信息。

■ 如果block的状态为UnderConstruction,那么至少收到一个副本上报信息,并且这个副本的状态为rwr或优于rwr,也就是rwr、committed、finalized其中之一。

3.3.3 客户端的错误

从第2章的GFS分析中可以看出,在串行serial,也就是操作一个接着一个,即在一个操作完成之后再进行下一个操作)写入时,GFS可以保证一致性。显而易见,串行是保证获得一致性的一种简单方法;保证只有一个写入者(即只有一个writer,这个writer同一时刻只能发起一个操作,采用单线程是比较简单的实现)是实现串行的一种简单方法;只启动一个writer是保证只有一个写入者的简单方法。

但是,只启用一个writer时,宕机会成为问题。如果writer可以快速恢复,则还好;但如果writer不能恢复,那么整个写入功能就无效了。解决writer宕机问题的方法是启用多个writer,为了保证只有一个写入者写入,需要引入同步机制(synchronization)或者叫作锁机制(locking),拿到锁的writer可以写入,没拿到锁的writer,要等待持有锁的writer发生宕机,再接替它。HDFS采用的这种方式,具体来说就是租约机制(见3.2.1节)。

然而,前面讲解的租约机制并不能严格保证只有一个写入者写入,问题出在租约过期上。在writer正常的情况下,它会不断地续约,保证租约不过期,但是一旦writer出现假死、过载、续约包在网络上丢失等情况,续约就会失败,其他的writer就会从NN处拿到新的租约。这时前一个writer仍然还活着(比如在丢包的情况下),或者从假死和过载中恢复过来,就会出现两个writer(这种情况也被称为脑裂)。HDFS采用叫作租约恢复(lease recovery)的过程来解决这个脑裂问题,防止旧的写入者再写入数据。

客户端出错还会导致另外一个问题,即block的副本不能完成一个完整的文件写入过程(3.2节中讲解了一个完整的文件写入过程:create/append block→副本的状态变为rwr,接收数据写入→写满数据后,副本的状态变为finalized→客户端上报信息后,block在NN上的状态变为Committed→DN上报信息后,block在NN上的状态变为Complete→客户端关闭文件)。HDFS需要处理导致这种写入过程中断的错误,处理过程叫作块恢复(block recovery)

在块恢复的过程中,需要将这个block保存在每个DN上的副本都进行恢复,每个副本的恢复过程叫作副本恢复(replica recovery)

总之,租约恢复过程可能包含一个块恢复过程,而一个块恢复过程会包含多个副本恢复过程。

1.租约恢复

如果NN发现一个文件的租约过期了,那么它会将这个租约的持有者设置为dfs(dfs代表HDFS系统,表明这个文件被系统持有,不同于无人持有)。即使这个客户端还活着(比如发生假死后恢复),它向NN发送的请求(比如获取新代戳、获取新block、关闭文件)也会被拒绝,因为这时客户端已经不再具有有效的租约。

之后,NN检查这个文件最后两个block的状态。

● 如果最后两个block的状态是Complete,则NN会关闭这个文件。其他block的状态应该都是Complete。

● 如果最后两个block的状态是Committed或者Complete,则会等待一段时间(与租约超时的时长一致),此时的租约持有者是dfs。当租约过期后,NN还会检查最后两个block的状态,如果仍然不是Complete状态,则会续租。连续若干次续租后,最后两个block的状态仍然不是Complete,则强制关闭这个文件。

● 如果最后一个block的状态是UnderConstruction,则开始块恢复过程,在块恢复过程中会将这个block的状态改为UnderRecovery,表明该block正在进行恢复操作。

● 如果最后一个block的状态是UnderRecovery,说明之前已经开始了块恢复过程,则开始一个新的块恢复过程。在尝试若干次之后会放弃恢复。

2.块恢复

NN从block的副本所在的所有DN中选择一个作为首要DN(Primary DatanodePD)。如果没有DN可选,则块恢复过程终止。

NN生成一个新代戳,然后将block的状态从UnderConstruction改为UnderRecovery,为将新代戳作为recoveryid。由此可见,新的块恢复过程会具有更新的代戳,具有新代戳的块恢复过程会抢占之前旧的块恢复过程。

PD让每个DN都执行副本恢复过程,执行副本恢复过程的副本处于rurreplica under recovery)状态。每个副本的DN执行完副本恢复后,都会返回给PD副本的状态,该状态中包含副本id、副本的代戳、副本的磁盘文件长度、恢复前状态。

PD收到每个DN的副本执行状态后,会根据不同异常做出相应处理:

● 所有DN在执行副本恢复过程中都返回了异常,则终止块恢复过程。

● 所有副本返回的文件长度都为0,则要求NN删除这个block。

● 所有副本的状态都为finalized,但是副本的长度却不一样,则终止块恢复过程。

如果不存在异常,则根据所有副本的恢复前状态,选择其中一个副本的文件长度,作为block的长度。基本原则是有更优状态的副本,就选择更优状态(状态优先级为finalized>rbw>rur)的副本;没有更优状态的,则选择长度最小的。

3.副本恢复

在副本恢复过程中,DN会做如下几件事情。

● DN检查是否存在这个待恢复block的副本。如果不存在,或者副本的代戳旧于请求中block的代戳,或者副本的代戳新于recoveryid,则返回PD异常。

● 停止数据写入。如果DN正在向这个副本中写入数据,则块恢复过程(或者说是副本恢复过程)会抢占客户端写入。从客户端的角度来看,出现DN错误时,需要执行pipeline恢复过程,在这个过程中要向NN获取新代戳。但是,此时这个文件的租约持有者已经是dfs,客户端获取新代戳的操作会失败,从而使得客户端写入失败。通过这样的机制可阻止脑裂的出现。

● 停止旧的块恢复过程。如果副本处于rur状态,说明之前已经执行副本恢复过程,则停止这个旧的块恢复过程。