在前面的文章 《HDFS DataNode 設計實現解析》中我們對文件操作進行了描述,但並未展開講述其中涉及的異常錯誤處理與恢復機制。本文將深入探討 HDFS 文件操作涉及的錯誤處理與恢復過程。
讀異常與恢復
讀文件可能發生的異常有兩種:
- 讀取過程中 DataNode 掛了
- 讀取到的文件數據損壞
HDFS 的文件塊多副本分散存儲機制保障了數據存儲的可靠性,對於第一種情況 DataNode 掛了只需要失敗轉移到其他副本所在的 DataNode 繼續讀取,而對於第二種情況讀取到的文件數據塊若校驗失敗可認定為損壞,依然可以轉移到讀取其他完好的副本,並向 NameNode 匯報該文件 block 損壞,后續處理由 NameNode 通知 DataNode 刪除損壞文件 block,並根據完好的副本來復制一份新的文件 block 副本。
因為讀文件不涉及數據的改變,所以處理起來相對簡單,恢復機制的透明性和易用性都非常好。
寫異常與恢復
之前的文章中對寫文件的過程做了描述,這個過程中可能發生多種不同的錯誤異常對應着不同的處理方式。先看看有哪些可能的異常?
異常模式
可能的異常模式如下所列:
- Client 在寫入過程中,自己掛了
- Client 在寫入過程中,有 DataNode 掛了
- Client 在寫入過程中,NameNode 掛了
對於以上所列的異常模式,都有分別對應的恢復模式。
恢復模式
當 Client 在寫入過程中,自己掛了。由於 Client 在寫文件之前需要向 NameNode 申請該文件的租約(lease),只有持有租約才允許寫入,而且租約需要定期續約。所以當 Client 掛了后租約會超時,HDFS 在超時后會釋放該文件的租約並關閉該文件,避免文件一直被這個掛掉的 Client 獨占導致其他人不能寫入。這個過程稱為 lease recovery。
在發起 lease recovery 時,若多個文件 block 副本在多個 DataNodes 上處於不一致的狀態,首先需要將其恢復到一致長度的狀態。這個過程稱為 block recovery。 這個過程只能在 lease recovery 過程中發起。
當 Client 在寫入過程中,有 DataNode 掛了。寫入過程不會立刻終止(如果立刻終止,易用性和可用性都太不友好),取而代之 HDFS 嘗試從流水線中摘除掛了的 DataNode 並恢復寫入,這個過程稱為 pipeline recovery。
當 Client 在寫入過程中,NameNode 掛了。這里的前提是已經開始寫入了,所以 NameNode 已經完成了對 DataNode 的分配,若一開始 NameNode 就掛了,整個 HDFS 是不可用的所以也無法開始寫入。流水線寫入過程中,當一個 block 寫完后需向 NameNode 報告其狀態,這時 NameNode 掛了,狀態報告失敗,但不影響 DataNode 的流線工作,數據先被保存下來,但最后一步 Client 寫完向 NameNode 請求關閉文件時會出錯,由於 NameNode 的單點特性,所以無法自動恢復,需人工介入恢復。
上面先簡單介紹了對應異常的恢復模式,詳細過程后文再描述。在介紹詳細恢復過程前,需要了解文件數據狀態的概念。因為寫文件過程中異常和恢復會對數據狀態產生影響,我們知道 HDFS 文件至少由 1 個或多個 block 構成,因此每個 block 都有其相應的狀態,由於文件的元數據在 NameNode 中管理而文件數據本身在 DataNode 中管理,為了區分文件 block 分別在 NameNode 和 DataNode 上下文語境中的區別,下面我們會用 replica(副本)特指在 DataNode 中的 block,而 block 則限定為在 NameNode 中的文件塊元數據信息。在這個語義限定下 NameNode 中的 block 實際對應 DataNodes 上的多個 replicas,它們分別有不同的數據狀態。我們先看看 replica 和 block 分別在 DataNode 和 NameNode 中都存在哪些狀態?
Replica 狀態
Replica 在 DataNode 中存在的狀態列表如下:
- FINALIZED:表明 replica 的寫入已經完成,長度已確定,除非該 replica 被重新打開並追加寫入。
- RBW:該狀態是 Replica Being Written 的縮寫,表明該 replica 正在被寫入,正在被寫入的 replica 總是打開文件的最后一個塊。
- RWR:該狀態是 Replica Waiting to be Recovered 的縮寫,假如寫入過程中 DataNode 掛了重啟后,其上處於 RBW 狀態的 replica 將被變更為 RWR 狀態,這個狀態說明其數據需要恢復,因為在 DataNode 掛掉期間其上的數據可能過時了。
- RUR:該狀態是 Replica Under Recovery 的縮寫,表明該 replica 正處於恢復過程中。
- TEMPORARY:一個臨時狀態的 replica 是因為復制或者集群平衡的需要而創建的,若復制失敗或其所在的 DataNode 發生重啟,所有臨時狀態的 replica 會被刪除。臨時態的 replica 對外部 Client 來說是不可見的。
DataNode 會持久化存儲 replica 的狀態,每個數據目錄都包含了三個子目錄:
- current:目錄包含了
FINALIZED狀態 replicas。 - tmp:目錄包含了
TEMPORARY狀態的 replicas。 - rbw:目錄則包含了
RBW、RWR和RUR三種狀態的 relicas,從該目錄下加載的 replicas 默認都處於RWR狀態。
從目錄看出實際上只持久化了三種狀態,而在內存中則有五種狀態,從下面的 replica 狀態變遷圖也可以看出這點。

我們從 Init 開始簡單描述下 replica 的狀態變遷圖。
- 從
Init出發,一個新創建的 replica 初始化為兩種狀態:
- 由 Client 請求新建的 replica 用於寫入,狀態為
RBW。 - 由 NameNode 請求新建的 replica 用於復制或集群間再平衡拷貝,狀態為
TEMPORARY。
- 由 Client 請求新建的 replica 用於寫入,狀態為
- 從
RBW出發,有三種情況:
- Client 寫完並關閉文件后,切換到
FINALIZED狀態。 - replica 所在的 DataNode 發生重啟,切換到
RWR狀態,重啟期間數據可能過時了,可以被丟棄。 - replica 參與 block recovery 過程(詳見后文),切換到
RUR狀態。
- Client 寫完並關閉文件后,切換到
- 從
TEMPORARY出發,有兩種情況:
- 復制或集群間再平衡拷貝成功后,切換到
FINALIZED狀態。 - 復制或集群間再平衡拷貝失敗或者所在 DataNode 發生重啟,該狀態下的 replica 將被刪除
- 復制或集群間再平衡拷貝成功后,切換到
- 從
RWR出發,有兩種情況:
- 所在 DataNode 掛了,就變回了
RBW狀態,因為持久化目錄rbw包含了三種狀態,重啟后又回到RWR狀態。 - replica 參與 block recovery 過程(詳見后文),切換到
RUR狀態。
- 所在 DataNode 掛了,就變回了
- 從
RUR出發,有兩種情況:
- 如上,所在 DataNode 掛了,就變回了
RBW狀態,重啟后只會回到RWR狀態,看是否還有必要參與恢復還是過時直接被丟棄。 - 恢復完成,切換到
FINALIZED狀態。
- 如上,所在 DataNode 掛了,就變回了
- 從
FINALIZED出發,有兩種情況:
- 文件重新被打開追加寫入,文件的最后一個 block 對應的所有 replicas 切換到
RBW。 - replica 參與 block recovery 過程(詳見后文),切換到
RUR狀態。
- 文件重新被打開追加寫入,文件的最后一個 block 對應的所有 replicas 切換到
接下我們再看看 NameNode 上 block 的狀態有哪些以及時如何變化的。
Block 狀態
Block 在 NameNode 中存在的狀態列表如下:
- UNDER_CONSTRUCTION:當新創建一個 block 或一個舊的 block 被重新打開追加時處於該狀態,處於改狀態的總是一個打開文件的最后一個 block。
- UNDER_RECOVERY:當文件租約超時,一個處於
UNDER_CONSTRUCTION狀態下 block 在 block recovery 過程開始后會變更為該狀態。 - COMMITTED:表明 block 數據已經不會發生變化,但向 NameNode 報告處於
FINALIZED狀態的 replica 數量少於最小副本數要求。 - COMPLETE:當 NameNode 收到處於
FINALIZED狀態的 replica 數量達到最小副本數要求后,則切換到該狀態。只有當文件的所有 block 處於該狀態才可被關閉。
NameNode 不會持久化存儲這些狀態,一旦 NameNode 發生重啟,它將所有打開文件的最后一個 block 設置為 UNDER_CONSTRUCTION 狀態,其他則全部設置為 COMPLETE 狀態。
下圖展示了 block 的狀態變化過程。

我們還是從 Init 開始簡單描述下 block 的狀態變遷圖。
- 從
Init出發,只有當 Client 新建或追加文件寫入時新創建的 block 處於UNDER_CONSTRUCTION狀態。 - 從
UNDER_CONSTRUCTION出發,有三種情況:
- 當客戶端發起 add block 或 close 請求,若處於
FINALIZED狀態的 replica 數量少於最小副本數要求,則切換到COMMITTED狀態,
這里 add block 操作影響的是文件的倒數第二個 block 的狀態,而 close 影響文件最后一個 block 的狀態。 - 當客戶端發起 add block 或 close 請求,若處於
FINALIZED狀態的 replica 數量達到最小副本數要求,則切換到COMPLETE狀態 - 若發生 block recovery,狀態切換到
UNDER_RECOVERY。
- 當客戶端發起 add block 或 close 請求,若處於
- 從
UNDER_RECOVERY,有三種情況:
- 0 字節長度的 replica 將直接被刪除。
- 恢復成功,切換到
COMPLETE。 - NameNode 發生重啟,所有打開文件的最后一個 block 會恢復成
UNDER_CONSTRUCTION狀態。
- 從
COMMITTED出發,有兩種情況:
- 若處於
FINALIZED狀態的 replica 數量達到最小副本數要求或者文件被強制關閉或者 NameNode 重啟且不是最后一個 block,
則直接切換為COMPLETE狀態。 - NameNode 發生重啟,所有打開文件的最后一個 block 會恢復成
UNDER_CONSTRUCTION狀態。
- 若處於
- 從
COMPLETE出發,只有在 NameNode 發生重啟,其打開文件的最后一個 block 會恢復成UNDER_CONSTRUCTION狀態。
這種情況,若 Client 依然存活,有 Client 來關閉文件,否則由 lease recovery 過程來恢復(詳見下文)。
理解了 block 和 replica 的狀態及其變化過程,我們就可以進一步詳細分析上述簡要提及的幾種自動恢復模式。
Lease Recovery 和 Block Recovery
前面講了 lease recovery 的目的是當 Client 在寫入過程中掛了后,經過一定的超時時間后,收回租約並關閉文件。但在收回租約關閉文件前,需要確保文件 block 的多個副本數據一致(分布式環境下很多異常情況都可能導致多個數據節點副本不一致),若不一致就會引入 block recovery 過程進行恢復。下面是整個恢復處理流程的簡要算法描述:
- 獲取包含文件最后一個 block 的所有 DataNodes。
- 指定其中一個 DataNode 作為主導恢復的節點。
- 主導節點向其他節點請求獲得它們上面存儲的 replica 信息。
- 主導節點收集了所有節點上的 replica 信息后,就可以比較計算出各節點上不同 replica 的最小長度。
- 主導節點向其他節點發起更新,將各自 replica 更新為最小長度值,保持各節點 replica 長度一致。
- 所有 DataNode 都同步后,主導節點向 NameNode 報告更新一致后的最終結果。
- NameNode 更新文件 block 元數據信息,收回該文件租約,並關閉文件。
其中 3~6 步就屬於 block recovery 的處理過程,這里有個疑問為什么在多個副本中選擇最小長度作為最終更新一致的標准?想想寫入流水線過程,如果 Client 掛掉導致寫入中斷后,對於流水線上的多個 DataNode 收到的數據在正常情況下應該是一致的。但在異常情況下,排在首位的收到的數據理論上最多,末位的最少,由於數據接收的確認是從末位按反方向傳遞到首位再到 Client 端。所以排在末位的 DataNode 上存儲的數據都是實際已被確認的數據,而它上面的數據實際在不一致的情況也是最少的,所以算法里選擇多個節點上最小的數據長度為標准來同步到一致狀態。
Pipeline Recovery
如上圖所示,pipeline 寫入包括三個階段:
- pipeline setup:Client 發送一個寫請求沿着 pipeline 傳遞下去,最后一個 DataNode 收到后發回一個確認消息。Client 收到確認后,pipeline 設置准備完畢,可以往里面發送數據了。
- data streaming:Client 將一個 block 拆分為多個 packet 來發送(默認一個 block 64MB,太大所以需要拆分)。Client 持續往 pipeline 發送 packet,在收到 packet ack 之前允許發送 n 個 packet,n 就是 Client 的發送窗口大小(類似 TCP 滑動窗口)。
- close:Client 在所有發出的 packet 都收到確認后發送一個 Close 請求,
pipeline 上的 DataNode 收到 Close 后將相應 replica 修改為FINALIZED狀態,並向 NameNode 發送 block 報告。NameNode 將根據報告的FINALIZED狀態的 replica 數量是否達到最小副本要求來改變相應 block 狀態為COMPLETE。
Pipeline recovery 可以發生在這三個階段中的任意一個,只要在寫入過程中一個或多個 DataNode 遭遇網絡或自身故障。我們來分別分析下。
從 pipeline setup 錯誤中恢復
在 pipeline 准備階段發生錯誤,分兩種情況:
- 新寫文件:Client 重新請求 NameNode 分配 block 和 DataNodes,重新設置 pipeline。
- 追加文件:Client 從 pipeline 中移除出錯的 DataNode,然后繼續。
從 data streaming 錯誤中恢復
- 當 pipeline 中的某個 DataNode 檢測到寫入磁盤出錯(可能是磁盤故障),它自動退出 pipeline,關閉相關的 TCP 連接。
- 當 Client 檢測到 pipeline 有 DataNode 出錯,先停止發送數據,並基於剩下正常的 DataNode 重新構建 pipeline 再繼續發送數據。
- Client 恢復發送數據后,從沒有收到確認的 packet 開始重發,其中有些 packet 前面的 DataNode 可能已經收過了,則忽略存儲過程直接傳遞到下游節點。
從 close 錯誤中恢復
到了 close 階段才出錯,實際數據已經全部寫入了 DataNodes 中,所以影響很小了。Client 依然根據剩下正常的 DataNode 重建 pipeline,讓剩下的 DataNode 繼續完成 close 階段需要做的工作。
以上就是 pipeline recovery 三個階段的處理過程,這里還有點小小的細節可說。
當 pipeline 中一個 DataNode 掛了,Client 重建 pipeline 時是可以移除掛了的 DataNode,也可以使用新的 DataNode 來替換。這里有策略是可配置的,稱為 DataNode Replacement Policy upon Failure,包括下面幾種情況:
- NEVER:從不替換,針對 Client 的行為
- DISABLE:禁止替換,DataNode 服務端拋出異常,表現行為類似 Client 的 NEVER 策略
- DEFAULT:默認根據副本數要求來決定,簡單來說若配置的副本數為 3,如果壞了 2 個 DataNode,則會替換,否則不替換
- ALWAYS:總是替換
總結
本文講述了 HDFS 異常處理與恢復的處理流程和細節,它確保 HDFS 在面對網絡和節點錯誤的情況下保證數據寫入的持久性和一致性。讀完本篇相信你會對 HDFS 內部的一些設計和工作狀態有更深的認識,本文特地抽象的描述了一些涉及具體算法的部分。因為 HDFS 作為一個開源軟件還在不斷發展演變,具體算法代碼實現總會變化,但設計理念和 design for failure 的設計思考要點的持續性要長久的多,會更具參考價值。如果你還想對某些特定細節的實現做進一步的了解,只能去深入閱讀對應部分的代碼,這無法通過閱讀文檔或相關文章來獲得,這也是代碼開源的價值所在。
參考
[1] Hadoop Documentation. HDFS Architecture.
[2] Robert Chansler, Hairong Kuang, Sanjay Radia, Konstantin Shvachko, and Suresh Srinivas. The Hadoop Distributed File System
[3] Tom White. Hadoop: The Definitive Guide. O’Reilly Media(2012-05), pp 94-96
[4] Yongjun Zhang. Understanding HDFS Recovery Processes
[5] Hairong
Kuang,
Konstantin
Shvachko,
Nicholas
Sze,
Sanjay
Radia,
Robert
Chansler
, Yahoo!
HDFS
team
Design Specification: Append/Hflush/Read
Design
[6] HDFSteam. Design Specification: HDFS Append and Truncates
下面是我自己開的一個微信公眾號 [瞬息之間],除了寫技術的文章、還有產品的、行業和人生的思考,希望能和更多走在這條路上同行者交流,有興趣可關注一下,謝謝。

版權聲明:本文為博主原創文章,未經博主允許不得轉載。
