天天看點

HDFS 異常處理與恢複

在前面的文章 《HDFS DataNode 設計實作解析》中我們對檔案操作進行了描述,但并未展開講述其中涉及的異常錯誤處理與恢複機制。本文将深入探讨 HDFS 檔案操作涉及的錯誤處理與恢複過程。

讀異常與恢複

讀檔案可能發生的異常有兩種:

  1. 讀取過程中 DataNode 挂了
  2. 讀取到的檔案資料損壞

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

  • RBW

    出發,有三種情況:
    • Client 寫完并關閉檔案後,切換到

      FINALIZED

    • replica 所在的 DataNode 發生重新開機,切換到

      RWR

      狀态,重新開機期間資料可能過時了,可以被丢棄。
    • replica 參與 block recovery 過程(詳見後文),切換到

      RUR

  • TEMPORARY

    出發,有兩種情況:
    • 複制或叢集間再平衡拷貝成功後,切換到

      FINALIZED

    • 複制或叢集間再平衡拷貝失敗或者所在 DataNode 發生重新開機,該狀态下的 replica 将被删除
  • RWR

    • 所在 DataNode 挂了,就變回了

      RBW

      狀态,因為持久化目錄

      rbw

      包含了三種狀态,重新開機後又回到

      RWR

    • RUR

  • RUR

    • 如上,所在 DataNode 挂了,就變回了

      RBW

      狀态,重新開機後隻會回到

      RWR

      狀态,看是否還有必要參與恢複還是過時直接被丢棄。
    • 恢複完成,切換到

      FINALIZED

  • FINALIZED

    • 檔案重新被打開追加寫入,檔案的最後一個 block 對應的所有 replicas 切換到

      RBW

    • RUR

接下我們再看看 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 的狀态。

    • FINALIZED

      狀态的 replica 數量達到最小副本數要求,則切換到

      COMPLETE

      狀态
    • 若發生 block recovery,狀态切換到

      UNDER_RECOVERY

  • UNDER_RECOVERY

    ,有三種情況:
    • 0 位元組長度的 replica 将直接被删除。
    • 恢複成功,切換到

      COMPLETE

    • NameNode 發生重新開機,所有打開檔案的最後一個 block 會恢複成

      UNDER_CONSTRUCTION

  • COMMITTED

    • 若處于

      FINALIZED

      狀态的 replica 數量達到最小副本數要求或者檔案被強制關閉或者 NameNode 重新開機且不是最後一個 block,

      則直接切換為

      COMPLETE

    • UNDER_CONSTRUCTION

  • COMPLETE

    出發,隻有在 NameNode 發生重新開機,其打開檔案的最後一個 block 會恢複成

    UNDER_CONSTRUCTION

    狀态。

    這種情況,若 Client 依然存活,有 Client 來關閉檔案,否則由 lease recovery 過程來恢複(詳見下文)。

了解了 block 和 replica 的狀态及其變化過程,我們就可以進一步詳細分析上述簡要提及的幾種自動恢複模式。

Lease Recovery 和 Block Recovery

前面講了 lease recovery 的目的是當 Client 在寫入過程中挂了後,經過一定的逾時時間後,收回租約并關閉檔案。但在收回租約關閉檔案前,需要確定檔案 block 的多個副本資料一緻(分布式環境下很多異常情況都可能導緻多個資料節點副本不一緻),若不一緻就會引入 block recovery 過程進行恢複。下面是整個恢複處理流程的簡要算法描述:

  1. 擷取包含檔案最後一個 block 的所有 DataNodes。
  2. 指定其中一個 DataNode 作為主導恢複的節點。
  3. 主導節點向其他節點請求獲得它們上面存儲的 replica 資訊。
  4. 主導節點收集了所有節點上的 replica 資訊後,就可以比較計算出各節點上不同 replica 的最小長度。
  5. 主導節點向其他節點發起更新,将各自 replica 更新為最小長度值,保持各節點 replica 長度一緻。
  6. 所有 DataNode 都同步後,主導節點向 NameNode 報告更新一緻後的最終結果。
  7. NameNode 更新檔案 block 中繼資料資訊,收回該檔案租約,并關閉檔案。

其中 3~6 步就屬于 block recovery 的處理過程,這裡有個疑問為什麼在多個副本中選擇最小長度作為最終更新一緻的标準?想想寫入流水線過程,如果 Client 挂掉導緻寫入中斷後,對于流水線上的多個 DataNode 收到的資料在正常情況下應該是一緻的。但在異常情況下,排在首位的收到的資料理論上最多,末位的最少,由于資料接收的确認是從末位按反方向傳遞到首位再到 Client 端。是以排在末位的 DataNode 上存儲的資料都是實際已被确認的資料,而它上面的資料實際在不一緻的情況也是最少的,是以算法裡選擇多個節點上最小的資料長度為标準來同步到一緻狀态。

Pipeline Recovery

如上圖所示,pipeline 寫入包括三個階段:

  1. pipeline setup:Client 發送一個寫請求沿着 pipeline 傳遞下去,最後一個 DataNode 收到後發回一個确認消息。Client 收到确認後,pipeline 設定準備完畢,可以往裡面發送資料了。
  2. data streaming:Client 将一個 block 拆分為多個 packet 來發送(預設一個 block 64MB,太大是以需要拆分)。Client 持續往 pipeline 發送 packet,在收到 packet ack 之前允許發送 n 個 packet,n 就是 Client 的發送視窗大小(類似 TCP 滑動視窗)。
  3. close:Client 在所有發出的 packet 都收到确認後發送一個 Close 請求,

    pipeline 上的 DataNode 收到 Close 後将相應 replica 修改為

    FINALIZED

    狀态,并向 NameNode 發送 block 報告。NameNode 将根據報告的

    FINALIZED

    狀态的 replica 數量是否達到最小副本要求來改變相應 block 狀态為

    COMPLETE

Pipeline recovery 可以發生在這三個階段中的任意一個,隻要在寫入過程中一個或多個 DataNode 遭遇網絡或自身故障。我們來分别分析下。

從 pipeline setup 錯誤中恢複

在 pipeline 準備階段發生錯誤,分兩種情況:

  1. 新寫檔案:Client 重新請求 NameNode 配置設定 block 和 DataNodes,重新設定 pipeline。
  2. 追加檔案:Client 從 pipeline 中移除出錯的 DataNode,然後繼續。

從 data streaming 錯誤中恢複

  1. 當 pipeline 中的某個 DataNode 檢測到寫入磁盤出錯(可能是磁盤故障),它自動退出 pipeline,關閉相關的 TCP 連接配接。
  2. 當 Client 檢測到 pipeline 有 DataNode 出錯,先停止發送資料,并基于剩下正常的 DataNode 重新建構 pipeline 再繼續發送資料。
  3. 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,包括下面幾種情況:

  1. NEVER:從不替換,針對 Client 的行為
  2. DISABLE:禁止替換,DataNode 服務端抛出異常,表現行為類似 Client 的 NEVER 政策
  3. DEFAULT:預設根據副本數要求來決定,簡單來說若配置的副本數為 3,如果壞了 2 個 DataNode,則會替換,否則不替換
  4. 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

下面是我自己開的一個微信公衆号 [瞬息之間],除了寫技術的文章、還有産品的、行業和人生的思考,希望能和更多走在這條路上同行者交流,有興趣可關注一下,謝謝。

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