25

HDFS Lease(租约)逻辑

 4 years ago
source link: http://lday.me/2020/01/28/0024_hdfs_lease_internal/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

HDFS对于同一个文件支持一写多读(write-once-read-many)。为了保持数据一致性,当一个客户端往HDFS某个文件写数据时, 其他客户端不允许同时写入。HDFS引入Lease(租约)机制来实现“独写”控制。

本文基于hadoop-2.7.2版本对HDFS的租约机制进行整理分析

Lease的组织形式

lease由NameNode( LeaseManager )负责维护。一个lease是一个client(由clientName决定,DFSClient_xxxx, DFSClient.java)可写的若干文件的凭证记录,他记录了该client(holder)对哪些文件具有写权限,同时,lease具有一定的独占期限(2s, SoftLimit ),如果该client未能在上一轮续约后2s内完成新的续约,该client拥有的可写权限将允许被其他用户抢占,同时,lease还具有一个失效期限,当client长时间未续约(60min, HardLimit ),NameNode将负责从后台直接清理掉该lease,client将不再具有对之前任何文件的写权限。

简单来说,client要正常拥有对文件的写权限,就需要每隔2s,完成一次lease续约(renew lease)。如果在2s之后,还没有进行续约,则其他client可以进行抢占,但如果没有其他client抢占,该client还是拥有这份lease,同样还是对文件具有写权限。如果60min之后,client还没有对lease进行续约,那么,NameNode将视该lease过期,并对该lease进行清理,此时client不再具有对文件的写权限。

为判断一份lease是否有效,lease记录最近一次,该client申请续约的时间( lastUpdate )。一个Lease的结构组成如下:

yURfYfN.png!web

  • holder: 记录了这是哪个client的租约信息
  • lastUpdate: 记录上一次续租时间
  • paths: lease包含的文件信息(这个client对哪些文件可写)

Lease是与client(holder)对应的,在后台NameNode上,每一个client有一份唯一的Lease与之对应。在这份Lease中,记录了该client拥有写权限的所有文件信息(paths)。为了维护client <–> Lease <–> files之间的关系,方便分别从client的角度,从file的角度去访问、判断lease信息,NameNode(LeaseManager)使用了3个数据结构来完成相关管理工作:

JnUj22Y.png!web

  • leases: 用于管理client –> lease之的关系
  • sortedLease: 按lastUpdate(最近一次续约时间),由早到晚排序的所有lease列表
  • sortedLeasesByPath: 用于管理file(path) –> lease之间的关系信息

通过leases结构,可以找到某个client的lease信息,进而可以找到,他对哪些file有写权限;通过sortedLease, 从前往后遍历,可以判断哪些lease已经过期,需要进行超期清理;通过sortedLeaseByPath,可以判断当前某个file是归属于哪个lease,进而判断是哪个client对该file有写权限。

Lease的创建(添加)

在发起对一个文件的持续写入之初,需要首先申请创建针对该文件的对应租约信息。如果该client在NameNode上已经有了对应的租约,则需要将当前的目标file添加到该租约中去,并更新最新续租时间。如果这是client第一次申请租约,则需要创建对应的租约,并将目标file添加到这个新创建的租约中。 LeaseManager 通过 addLease 接口完成上述逻辑,在 addLease 接口中,将针对性的调整 leases , sortedLease , sortedLeaseByPath 三个结构的内容。

client端,发起对文件的持续写入主要涉及与NameNode的3个RPC接口(service ClientNamenodeProtocol):

  • create, 创建新文件,用于写入
  • append,已追加写的方式打开已有文件
  • tuncate,对当前文件执行阶段操作

create

用户在创建新文件用于写入时,会通过 DistributedFileSystem::create(...) 调用,获取对应的输出流,进而执行后续的数据写入。在此过程中 DistributedFileSystem 会调用 DFSClient::create ,并最终在 DFSOutputStream 中调用 dfsClient.namenode.create(...) ,向NameNode发起create RPC调用。

NameNodeRpcServer执行create处理,在执行过程中,会首先通过 recoverLeaseInternal 来判断当前client是否可以获取到该file的lease。

  1. 首先判断该file是否已有lease存在,
  2. 如果没有,当然一切ok。
  3. 如果有,是否过期。
  4. 显然如果没过期,当前用户是无法抢占的。
  5. 如果已过期,判断是否能够立马关闭当前文件,从而获取lease,
  6. 如果不行,则发起lease recovery。发起lease recovery,意味着当前用户也无法立马获取到该文件的lease。( 关于 recoverLeaseInternal 的具体说明,参见后面2.4的章节

当一切检查都通过, client满足获取lease的条件后,则创建client与file的lease,将其加入到 LeaseManager 的管理中

a6bYn2V.png!web

在调用 recoverLeaseInternal 时,如果未能立即获取到lease,将抛出异常。

append

append用于追加写的方式打开一个已经存在的文件,对应逻辑与create基本类似。

MzUBJfB.png!web

truncate

truncate与create/append不太一样,详细说明可参考: HDFS truncate(HDFS-3107) 。NameNode在处理Truncate动作时,会根据实际truncate边界进行不同的处理,如果truncate的边界,正好在一个block的尾巴上(两个block的连接处),不需要对某个block进行截断裁剪的话。则此时要做的事情,就是直接截断,更新block信息。但如果不是在边界上,需要对block中的数据进行截取,则会由NameNode发起一个BlockRecovery过程( initializeBlockRecovery ),根据truncate的设置,对block进行再定长,完成所有副本的truncate操作。

两种场景,对应了truncate的两个返回值:

  1. return true。如果裁剪在边界上, truncate能够直接完成,并不会去获取lease信息,只确保能够拿到lease就ok,因为在truncate返回之后,truncate实际已经执行完成,无需持有lease。

  2. return false。truncate没有直接完成,只是发起了block recovery动作,因此需要在block recovery的整个过程中持有lease,用户需等待truncate完成之后,重新append打开文件(再次获取lease),进行数据再追加。

When the truncate operation returns to the client the file may or may not be immediately available for write. If the newLength happened to be on the block boundary, then the file can be opened for append immediately. Otherwise clients should wait for the truncate recovery to complete. During the recovery an attempt to append will result inRecoveryInProgressException.

对于第1种情况:

rmM7naI.png!web

对于第2中场景:

由于需要在执行block recovery过程中,确保没有其他用户的持有lease,因此,需要添加lease操作:

IR7FruY.png!web

如上图所示,如果truncate在block中间,则会由NameNode发起block recovery过程,在发起block recovery之前,会在 prepareFileForTruncate 中首先申请添加Lease。Block Recovery任务会随着DataNode的心跳响应异步的下发至DataNode,并执行。在DataNode完成Block Recovery之后,会向NameNode汇报结果( commitBlockSynchronization ),NameNode在此完成File的数据更新,并最终关闭文件,将Lease释放。( 具体的Block Recovery流程,可参见后续2.4.2章节描述。 )

recoverLeaseInternal

recoverLeaseInternal 在整个Lease管理中的作用非常重要,他用于判断是否可以接管lease。

  1. 当前用户clientA是否可以立马获取到对应文件的租约?
  2. 如果当前file不在写入状态( UnderConstruction ),那显然,当前用户是可以拿到该文件的Lease的
  3. 如果当前file处于UC状态,说明 可能 正在有数据向该文件写入
    1. 如果是要强制抢占( force==true ),此时不管太多,尝试直接接管( internalReleaseLease )
    2. 有数据写入(UC状态),则判断该file的lease是否超期( softLimit )
      1. 如果没有超期,显然clientA不能抢占lease
        RecoveryInProgressException
        AlreadyBeingCreatedException
        
      2. 如果clientB的lease已经超期了,那么就让clientA尝试直接接管(internalReleaseLease)

internalReleaseLease

internalReleaseLease 的任务是 尝试直接接管 ,如果能够直接接管,返回true。如果目前暂时无法直接接管,需要进行block recovery,则发起block recovery,并返回false。返回false,表示已经启动了lease recovery,没法直接接管,需要等待。其他情况,则抛出异常。

  1. 直接接管(立刻拿到lease)的条件是,可以将当前file直接close掉。这里主要是看file中block的状态,
    1. 如果file的所有block都为completed,那显然是可以立马close的。
    2. 如果只有file的最后一个block是committed,其他都是completed状态,那进一步看看是否至少有一个datanode提交了最后一个block,保证满足了最小有效副本数,如果有了, 那也可以close了。否则就需要进行block recovery
    3. 另外还有一种情况,如果最后一个block不是commit,长度为0,并且没有DataNode汇报过该block的信息,这说明,client在写数据前,就故障了,此时可以将该block删除,并close掉,也可以直接接管
  2. 如果最后一个Block处于 UNDER_CONSTRUCTION 或者 UNDER_RECOVERY ,已经有数据写入了,显然没法直接接管,需要通过block recovery, uc.initializeBlockRecovery(...) 对最后一个block的长度进行协商判定,确认之后,再将file close掉。这会是一个异步过程,无法直接接管。
  3. 其他一些异常情况,则抛出异常

Block Recovery过程

从上面 internalReleaseLease 的逻辑描述我们可以看出,在尝试直接接管的时候,存在当前文件的最后一个Block已经有数据写入的情况,3个Block副本的数据写入状态不一定一致,此时将由NameNode发起Block Recovery,Block Recovery流程大致如下:

  1. NameNode在 internalReleaseLease 中启动BlockRecovery:

    1. 拿到一个新的 GnernationStamp ,作为recovery block使用的GS。 nextGenerationStamp(blockIdManager.isLegacyBlock(uc))
    2. 将文件的所有权(lease)赋给recovery调用方:如果是用户抢占触发的,则所有权保持不变,还是之前的client,如果是强制抢占触发的,则所有权为本次强制抢占的调用方,如果是后台Lease超期清理触发的,则所有权为 NAMENODE_LEASE_HOLDER(HDFS_NameNode)
    3. 接着 initializeBlockRecovery
      setBlockUCState(BlockUCState.UNDER_RECOVERY)
      primary.getExpectedStorageLocation().getDatanodeDescriptor().addBlockToBeRecovered(this)
      
  2. NameNode收到目标DataNode的心跳: handleHeatbeat

    getLeaseRecoveryCommand(Integer.MAX_VALUE)
    brCommand.add(new RecoveringBlock(...)
    
  3. DataNode接收NameNode的心跳响应,并处理相关指令: processCommandFromActive

    1. 执行recovery指令(case DatanodeProtocol. DNA_RECOVERBLOCK )。 dn.recoverBlocks
      • 首先通知该block的其他dn进入Replica Recovery,并获取各Replica的状态信息。 callInitReplicaRecovery –> datanode.initReplicaRecovery(rBlock)
        1. 其他dn在收到 initReplicaRecovery 调用后,执行处理( FsDatasetImpl::initReplicaRecovery )。
        2. 首先会停止dn上当前的数据传输 ( rip.stopWriter(xceiverStopTimeout) )
        3. 之后汇总replica信息,并汇报给block recovery的发起方(primary datanode)
      • primary datanode汇总汇报的信息,并基于汇总信息对该Block定长,最终将定长结果同步更新到其他dn。 syncBlock(...)
        1. 首先根据汇报的信息确定长度,
          case FINALIZED:newBlock.setNumBytes(finalizedLength);
          newBlock.setNumBytes(minLength)
          newBlock.setNumBytes(rBlock.getNewBlock().getNumBytes())
          
        2. 之后将定长结果同步到所有dn。 r.updateReplicaUnderRecovery(...)
        3. 同步好之后,将最终结果汇报给NameNode,告知NameNode根据汇报结果,close文件。 nn.commitBlockSynchronization(..., close = true, ...)
  4. NameNode收到DataNode的 commitBlockSynchronization 请求,执行最终的处理

    1. 根据汇报的信息,设置Block的GS,length等信息。 storedBlock.setGenerationStamp(newgenerationstamp);storedBlock.setNumBytes(newlength);
    2. 执行文件的关闭动作。 closeFileCommitBlocks(...)
      • 在执行关闭动作时,会释放该file的对应的lease。 finalizeINodeFileUnderConstruction –> leaseManager.removeLease 。这里的lease,可能是HDFS_NameNode(Lease清理触发),也可能是原本持有该client的lease(clientB尝试抢占触发),也可能是强行recoverLease的调用方(调用recoverLease触发)

至此,整个Block Recovery结束,file上的Lease被清理,且文件关闭,长度被确定。

Q1. 如果Block Recovery过程中,原来的clientA恢复过来了,会出现什么情况?

恢复过来也没有用,进入Block Recovery状态后,DataNode就会将之前的写入给断掉,并汇报最终状态到NameNode,NameNode会最终将文件close掉,释放文件上的Lease。因此即使ClientA恢复过来,也无法继续向文件写入数据,文件最终一定会到close状态。

Q2. Block Recovery过程如果时间过长,在此过程中,是否还能被其他人抢占?

HDFS_NameNode作为Lease持有者,执行恢复也好, 强制执行 recoverLease 的调用方ClientB作为持有者也好,甚至是主动抢占时触发Block recovery,保持原持有者ClientA作为持有者也好。如果Block Recovery没有在2s内完成,则也是可以被抢占的。只是这时候的抢占没有意义。原因是,再度进入internalReleaseLease逻辑,又会认为还需进行block recovery,进而又会发起新的一轮Block Recovery。只要上一轮的Block Recovery结束,NameNode处理 commitBlockSynchronization ,完成文件的close( finalizeINodeFileUnderConstruction ),后续的抢占,就不再属于Block Recovery过程的抢占,而是正常抢占。

Lease的续签

client在调用上述接口完成Lease申请之后,需要每隔2s,执行一次续签动作,否则对应的Lease将会失效。为此,在client端调用append/create之后,都将触发client端的LeaseRenewer线程,由 LeaseRenewer 线程不停的定时执行续签动作( namenode.renewLease )。

在DFSClient执行create/append,完成对应的create/append RPC调用之后,都将触发启动LeaseRenewer线程( beginFileLease )。LeaseRenewer是一个单例线程,会在第1次被调用时启动,之后不断的执行renew,他会判断对应的DFSClient的Lease当前时间距离上一轮续租是否超过1/2 SoftLimit(1s),如果超过,则发起下一轮续租请求。

从Lease续签的动作我们可以看出,续签是以Lease,也就是以client为单位的,不涉及对其中某个文件的续租,而是整体的续租。具体续租了哪些文件,这完全由NameNode(LeaseManager)中,该Lease对应的文件信息来决定。如果,通过其他调用,修改了NameNode上,该Lease对应的文件信息,例如移除了其中的某个文件file1。那么,即使该Client端还在不断的续租,而实际上,Client对file1的写权限,已经没有了。

Lease的主动释放

对于申请了lease的三个接口:create/append/truncate,都需要有与之对应的Lease主动释放。其中,从上面truncate的逻辑描述我们可以看出,如果truncate在block边界。实际并未申请Lease,因此也不需要释放Lease。而如果truncate在block中间,从上面的流程我们也可以看到,当DataNode完成Block Recovery之后,会有NameNode完成Lease的释放( finalizeINodeFileUnderConstruction 中)。

而对于调用了create/append的用户,则需要主动的调用DFSOutputStream::close/abort,在close/abort时,会调用 complete RPC接口,完成Lease的释放

UBNJviZ.png!web

结束Lease续签

LeaseRenewer 会记录每个DFSClient究竟打开了哪些文件,当 DFSOutputStream::close 时,会将打开的文件一并从 LeaseRenewer 中移除( endFileLease ),因此。当 LeaseRenewer 发现DFSClient上没有打开的文件时( dfsc.isFilesBeingWrittenEmpty() ),将不再对DFSClient发起Lease续签( dfsclients.remove(dfsc) )

Lease的校验

当用户通过create/append发起对文件的写入后,在持续不断的写入block过程中,涉及到若干RPC调用,其中与NameNode交互,涉及到元数据变更的调用都将执行Lease的校验,其包括:

addBlock
getAdditionalDatanode
abandonBlock
complete
fsync

NameNode在处理上述RPC调用时,都将执行Lease的校验( LeaseManager::checkLease ),校验过程:

  1. 校验的文件是否存在(inode == null).
  2. 校验目标是否为文件(非目录)(!inode.isFile())
  3. 校验的文件是否处于可写状态(!file.isUnderConstruction())
  4. 校验的文件是否处于待删除状态(isFileDeleted(file))
  5. 校验文件Lease的实际持有者,与当前目标是否一致 (holder != null && !clientName.equals(holder))
    • 在这一步校验中,并没有校验File当前的Lease是否已经超过 SoftLimit 。因此对于Lease是否有效,我们可以认为,只要Lease存在,Lease就是有效的,只是超过SoftLimit,是允许其他client抢占。

Afie6bR.png!web

在上述校验过程中出现失败,意味着对应的File已经进入了Block Recovery状态,由其他人接管了该文件。而在Block Recovery状态时,DN已经停止了之前的数据写入,并在停止写入之后,对Block进行了定长判断。定长被认的数据,就是”真实”写入的数据,定长被丢掉的数据,就是未写入的数据。数据长度的判定,并不发生在原client checkLease 失败的地方,而是发生在接管,BlockRecovery执行的过程中。 checkLease 的失败,只是最终结果,实际上,数据传输已在Block Recovery的时候被停止掉。

Lease的抢占(SoftLimit超时)

当clientA对他注册在NameNode上的Lease超过2s没有续租时,其他用户(ClientB)就可以对其中的file lease进行抢占。抢占的过程,就发生在create/append/truncate调用时,由 recoverLeaseInternal 负责执行,其具体过程参考 2.4.1 recoverLeaseInternal章节的说明 。抢占可以直接完成,此时clientB直接拥有了对file的写权限,继续自己的后续写入。抢占也可能不直接完成,此时 recoverLeaseInternal 会触发block recovery。而block recovery完成之后,文件处于close状态, clientB可再次尝试申请Lease。

Lease的超期清理(HardLimit超时)

LeaseManager 还有一个 HardLimit(1h)LeaseManager 中的monitor线程,会每隔2s启动一次,判断NameNode上的Lease是否有超过 HardLimit(checkLeases()) ,如果有,则执行超期清理工作。

  1. 执行清理工作主要基于sortedLeases来完成,由于sortedLeases已经是根据续约时间排好序的,因此只需要从sortedLeases头部取出Lease判断是否超期,如果头部的Lease都没有超期 (!leaseToCheck.expiredHardLimit()) ,则后续的Lease都不会超期。
  2. 对于超期Lease的处理, LeaseManager$Monitor 将拿出Lease中所有的file,因为Lease超期,意味着Lease中的所有文件都要释放掉。
  3. 针对每个File, LeaseManager$Monitor 同样是依赖于 internalReleaseLease ,完成超期释放动作:
    a. 如果可以直接释放,则直接释放。
    b. 如果不可以直接释放,则通过Block Recovery完成释放,此时Block Recovery释放的Lease持有者为(HDFS_NameNode)。
  4. 如果 internalReleaseLease 的处理遇到异常,则 LeaseManager$Monitor 会强行将对应的Lease删除 (removeLease(leaseToCheck, p);)

Lease的强制清理

有2种情况会执行Lease的强制清理:

  1. 删除该文件(包括删除对应的目录)
  2. 执行recoverLease调用

删除文件(或者目录)

当文件被删除时,该文件对应的Lease将强制被删除掉。当有目录被删除时,该目录上所有文件的Lease都将一并删除掉( removeLeaseWithPrefixPath )

QbErmi3.png!web

强制释放Lease

NameNode提供了recoverLease RPC调用来强制释放一个client持有某个file的Lease,无论这个Lease是否在效期内(SoftLimit, 2s)。 recoverLease达到的效果:

  1. return true,直接完成lease释放,文件已进入close状态
  2. return false,启动Block Recovery,进行异步的lease释放,待Block Recovery完成,文件将进入close状态,lease释放。
  3. 抛出异常,强制释放都出现问题@.@…

调用recoverLease并不意味着立马完成close,如果返回true是立马完成, 如果返回false,还得等待,知道block recovery异步完成,文件close。

N7NrYjV.png!web

Lease的变更

rename不会丢失Lease,他会一并对rename涉及的所有文件的Lease做统一调整( changeLease ), changeLease 会将原始前缀为src的所有file找出来( findLeaseWithPrefixPath ),并将这些目标file的Lease调整变更为指定dst前缀。

eA7R73u.png!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK