Hdfs append調(diào)用異常AlreadyBeingCreatedException
首先拋出的異常如下:
org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.hdfs.protocol.AlreadyBeingCreatedException): Failed to APPEND_FILE /binlogsync_test/binlog/mock/test/test_1502173606572 for DFSClient_NONMAPREDUCE_-70835360_1 on 127.0.0.1 because DFSClient_NONMAPREDUCE_-70835360_1 is already the current lease holder.
at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.recoverLeaseInternal(FSNamesystem.java:2863)
at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.appendFileInternal(FSNamesystem.java:2664)
at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.appendFileInt(FSNamesystem.java:2962)
at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.appendFile(FSNamesystem.java:2927)
at org.apache.hadoop.hdfs.server.namenode.NameNodeRpcServer.append(NameNodeRpcServer.java:652)
at org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolServerSideTranslatorPB.append(ClientNamenodeProtocolServerSideTranslatorPB.java:421)
at org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos$ClientNamenodeProtocol$2.callBlockingMethod(ClientNamenodeProtocolProtos.java)
at org.apache.hadoop.ipc.ProtobufRpcEngine$Server$ProtoBufRpcInvoker.call(ProtobufRpcEngine.java:616)
at org.apache.hadoop.ipc.RPC$Server.call(RPC.java:969)
at org.apache.hadoop.ipc.Server$Handler$1.run(Server.java:2049)
at org.apache.hadoop.ipc.Server$Handler$1.run(Server.java:2045)
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.Subject.doAs(Subject.java:422)
at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1657)
at org.apache.hadoop.ipc.Server$Handler.run(Server.java:2043)
首先解釋一下我是在什么情況下出現(xiàn)的這個(gè)異常:
我編寫了一個(gè)測試用例:模擬在往hdfs文件中寫了一條不完整的記錄之后,按照之前ack文件回滾到最近一條完整記錄,然后接著寫沟涨。文件操作的調(diào)用線路的這樣的:
1. FileSystem # create打開文件
1. FsDataoutputStream # write(寫若干條不完整記錄佑力,并且調(diào)用hsync確保落盤)
2. rollBack(根據(jù)ack文件回滾到最近一次的完整記錄,這個(gè)過程中調(diào)用了FsDataoutputStream # close關(guān)閉流俊马,然后FileSystem # truncate截?cái)辔募?
3. 以append的方式再次打開文件,寫若干條完整記錄,hsync落盤萝嘁,并且更新ack文件。(以上異常出現(xiàn)在這一步)
4. 對文件內(nèi)容和預(yù)期內(nèi)容做相等斷言
以上所有操作都是在一個(gè)線程中完成扬卷,使用同一個(gè)FileSystem的實(shí)例對象牙言,因此dfs client是同一個(gè),之所以提到這個(gè)是因?yàn)閔dfs的租約管理是以dfs client以及inode id來定位的怪得。
異常原因
這個(gè)異常的是由lease機(jī)制導(dǎo)致的咱枉,由namenode上rpc server(對應(yīng)NameNodeRpcServer的實(shí)現(xiàn))拋出來的卑硫。
hdfs通過lease機(jī)制來保證同一個(gè)文件某一時(shí)刻只會有一個(gè)客戶端執(zhí)行寫操作,client端調(diào)用append和create方法或者是FileSystem # truncate時(shí)蚕断,rpc server端都會添加一個(gè)新的lease欢伏。當(dāng)前client調(diào)用create之后獲得了lease,就不可以再調(diào)用append了亿乳,可以看看下面的拋出異常位置的代碼:
//這個(gè)方法rpc server端相應(yīng)create和append時(shí)都會調(diào)用
boolean recoverLeaseInternal(RecoverLeaseOp op, INodesInPath iip,
String src, String holder, String clientMachine, boolean force)
throws IOException {
assert hasWriteLock();
INodeFile file = iip.getLastINode().asFile();
//isUnderConstruction()硝拧,UnderConstruction狀態(tài)表示文件正在被某個(gè)客戶端寫(append,truncate,write)
if (file.isUnderConstruction()) {
//holder是lease擁有者,也就是嘗試寫文件的客戶端的name葛假,對應(yīng)DFSClient的name字段障陶。
Lease lease = leaseManager.getLease(holder);
if (!force && lease != null) {
/*獲得當(dāng)前寫操作的文件的lease,如果這個(gè)lease和holder擁有的lease是一樣的聊训,表示這個(gè)文件之前已經(jīng)由holder這個(gè)客戶端寫咸这,之前的那次寫請求使他獲得了lease,那么這個(gè)時(shí)候再調(diào)用一次寫就會拋這個(gè)異常(也就是本文開始的異常).
這是可以理解的魔眨,即便是同一個(gè)用戶也不應(yīng)該同時(shí)使用多個(gè)寫接口去寫文件媳维,這樣顯然會導(dǎo)致寫的內(nèi)容不正確
*/
Lease leaseFile = leaseManager.getLease(file);
if (leaseFile != null && leaseFile.equals(lease)) {
// We found the lease for this file but the original
// holder is trying to obtain it again.
throw new AlreadyBeingCreatedException(
op.getExceptionMessage(src, holder, clientMachine,
holder + " is already the current lease holder."));
}
}
//
// Find the original holder.
//
FileUnderConstructionFeature uc = file.getFileUnderConstructionFeature();
String clientName = uc.getClientName();
lease = leaseManager.getLease(clientName);
//這是另外一種情況,嘗試寫文件的用戶確沒有l(wèi)ease(可能是lease過期了)遏暴,那用戶就不能寫文件侄刽。
if (lease == null) {
throw new AlreadyBeingCreatedException(
op.getExceptionMessage(src, holder, clientMachine,
"the file is under construction but no leases found."));
}
if (force) {
// close now: no need to wait for soft lease expiration and
// close only the file src
LOG.info("recoverLease: " + lease + ", src=" + src +
" from client " + clientName);
return internalReleaseLease(lease, src, iip, holder);
} else {
assert lease.getHolder().equals(clientName) :
"Current lease holder " + lease.getHolder() +
" does not match file creator " + clientName;
//
// If the original holder has not renewed in the last SOFTLIMIT
// period, then start lease recovery.
//
if (lease.expiredSoftLimit()) {
LOG.info("startFile: recover " + lease + ", src=" + src + " client "
+ clientName);
if (internalReleaseLease(lease, src, iip, null)) {
return true;
} else {
throw new RecoveryInProgressException(
op.getExceptionMessage(src, holder, clientMachine,
"lease recovery is in progress. Try again later."));
}
} else {
final BlockInfo lastBlock = file.getLastBlock();
if (lastBlock != null
&& lastBlock.getBlockUCState() == BlockUCState.UNDER_RECOVERY) {
throw new RecoveryInProgressException(
op.getExceptionMessage(src, holder, clientMachine,
"another recovery is in progress by "
+ clientName + " on " + uc.getClientMachine()));
} else {
throw new AlreadyBeingCreatedException(
op.getExceptionMessage(src, holder, clientMachine,
"this file lease is currently owned by "
+ clientName + " on " + uc.getClientMachine()));
}
}
}
} else {
return true;
}
}
再回顧我的調(diào)用鏈:
FileSystem # create -> 獲得lease
FsDataOutputStream # close -> lease失效
FileSystem # truncate -> 獲得lease
FileSystem # append -> 獲得lease的情況下,再次嘗試新的接口寫朋凉,拋出異常州丹。
但是也有例外,比如下面這樣的調(diào)用就不會出現(xiàn)這種異常:
- truncate(path, 1) -> truncate(path, 1) . 連續(xù)兩次的trunc成同樣長度杂彭,是不會走到recoverLeaseInternal方法調(diào)用的墓毒,因?yàn)榕袛辔募呀?jīng)是那么大之后就直接返回,不做trunc亲怠。
- create(path, true)所计,指定true表示文件存在時(shí)overwrite,這個(gè)時(shí)候就算之前有client獲得lease团秽,指定overwrite會把之前文件刪除主胧,一并清除lease,所以也不會報(bào)錯(cuò)习勤。
后續(xù): 盡管知道了是因?yàn)閠runcate導(dǎo)致了append的異常踪栋,但是卻不知道怎么remove掉lease,最后的解決辦法居然是等待lease過期图毕,因?yàn)閠runcate操作不會不停的renew lease(續(xù)約)夷都。