我們從動態的角度來看 hdfs
先從場景出發,我們知道 hdfs 的寫檔案的流程是這樣的:
資料以 pipeline 的方式寫入 hdfs ,然後對於讀取操作,客戶端選擇其中一個儲存塊副本的 DataNode 來讀資料.考慮這樣兩個場景:
- hbase rs 在寫 wal log 的時候.如果一個 rs 掛了.那麼這個 rs 會轉移並且透過讀取 wal log 來恢復之前的狀態.如果這個rs 掛的時候 ,寫 wal log 的 pipeline 沒有完成,那麼必然這份 wal log 資料在不同的dn 上是存在差異的. 那麼 hdfs 是如何保證 rs 轉移後能夠恢復到正確的狀態?
- 流計算寫入hdfs ,如果中間 datanode 掛了.hdfs 是如何保證這個流計算程式不丟擲錯誤,並持續執行下去的?
這裡就引出了 hdfs 一個非常重要的特性就是 hdfs 寫的錯誤恢復.對於 hdfs 的寫的錯誤恢復.進而就需要了解三個重要概念: lease recovery, block recovery, and pipeline recovery . hdfs 的寫的容錯性就是由這三個概念保證的. 這三個概念也是相互關聯,相互包含的.一切跟寫檔案有關:
- 租約恢復 在客戶端可以寫入 HDFS 檔案之前,它必須獲得租約,這本質上是一個鎖。如果客戶端希望繼續寫,則必須在約定的時間段內續租。如果租約沒有明確更新或持有它的客戶端死亡了,那麼它就會過期。發生這種情況時,HDFS 將代表客戶端關閉檔案並釋放租約,以便其他客戶端可以寫入該檔案。這個過程稱為租約恢復。
- 塊恢復 如果正在寫入的檔案的最後一個塊沒有傳遞到管道中的所有 DataNode,那麼當發生租約恢復時,寫入不同節點的資料量可能會不同。在租約恢復導致檔案關閉之前,需要一個過程來確保最後一個塊的所有副本具有相同的長度.此過程稱為塊恢復。塊恢復僅在租約恢復過程中觸發,並且在租約恢復中僅在檔案的最後一個塊不處於 COMPLETE 狀態時才觸發塊恢復。
- 管道恢復 在寫入管道操作期間,管道中的某些 DataNode 可能會失敗。發生這種情況時,底層的寫操作不能只是失敗。相反,HDFS 將嘗試從錯誤中恢復,以允許管道繼續執行並且客戶端繼續寫入檔案。從管道錯誤中恢復的機制稱為管道恢復。
我們知道寫檔案,就是寫 block . 上面這些錯誤恢復,最終的目的無非是要保證所有客戶端的檔案的所有 block 都能夠完整的寫入所有的 datanode . 所以,還得從更細緻的角度去看 block,瞭解 block 的一些概念及語義
首先,把 datanode 中的 block 稱之為 replica(副本) .用以區分 namenode 中的 block(塊). 對於 replica ,它有如下幾種狀態,也對應了 replica 寫入到 datanode 的一個動態過程:
- FINALIZED 當副本處於此狀態時,對副本的寫入完成並且副本中的資料被“凍結”(長度已確定),除非重新開啟副本以進行追加。具有相同 generation stamp 的塊的所有最終副本(稱為 GS)應該具有相同的資料。最終副本的 GS 可能會因恢復發生而增加。
- RBW (Replica Being Written) 這是正在寫入的任何副本的狀態,無論檔案是為寫入而建立的,還是為追加而重新開啟的。 RBW 副本始終開啟檔案的一個塊。資料仍在往副本里面寫,尚未最終確定。 RBW 副本的資料(不一定是所有)對讀取客戶端可見。如果發生任何故障,將嘗試將資料儲存在 RBW 副本中。
- RWR (Replica Waiting to be Recovered) 如果一個 DataNode 死掉並重新啟動,它的所有 RBW 副本都將更改為 RWR 狀態。 RWR 副本要麼過時並因此被丟棄,要麼將參與租約恢復中的塊恢復。
- RUR (Replica Under Recovery) 非 TEMPORARY 副本在參與租約恢復時將更改為 RUR 狀態。
- TEMPORARY 臨時副本,用於塊複製,由 replication monitor 或cluster balancer 來發起。它類似於 RBW 副本,只是它的資料對所有讀取器客戶端都是不可見的。如果塊複製失敗,將刪除一個 TEMPORARY 副本。
以上就是 datanode 的 副本狀態,接著對比一下 namenode 的塊狀態:
- UNDER_CONSTRUCTION 這是寫入時的狀態。 UNDER_CONSTRUCTION 塊是開啟檔案的最後一個塊;它的長度和 GS 仍然是可變的,並且它的資料(不一定是全部)對讀者是可見的。 NameNode 中的 UNDER_CONSTRUCTION 塊會跟蹤管道中的合法 RBW 及 RWR 副本的位置。
- UNDER_RECOVERY 如果一個檔案的最後一個塊在相應客戶端的租約到期時處於 UNDER_CONSTRUCTION 狀態,那麼就會開始塊恢復,同時它將變為 UNDER_RECOVERY 狀態。
- COMMITTED COMMITTED 意味著一個塊的資料和 GS 不再可變(除非它被重新開啟用以追加, 並且此時上報上來的有相同 GS/長度的 FINALIZED 副本的 DataNode 數要少於設定的最小副本數。為了服務讀取請求,COMMITTED 塊必須跟蹤 RBW 副本的位置、GS 及其 FINALIZED 副本的長度。當客戶端要求 NameNode 向檔案新增新的塊或關閉檔案時,UNDER_CONSTRUCTION 塊將更改為 COMMITTED。如果最後一個或倒數第二個塊處於 COMMITTED 狀態,則無法關閉檔案,客戶端必須進行重試。
- COMPLETE 當 NameNode 檢測到 匹配 GS/長度要求的 FINALIZED 副本數達到最小副本數的要求時,COMMITTED 塊更改為 COMPLETE。只有當檔案的所有塊都變為 COMPLETE 時才能關閉檔案。一個塊可能會被強制進入 COMPLETE 狀態,即使它沒有最小的複製副本數 . 例如,當客戶端請求一個新塊時,前一個塊尚未完成這種情況.
DataNode 將副本的狀態儲存到磁碟,但 NameNode 不會將塊狀態儲存到磁碟。當 NameNode 重新啟動時,它將先前所有開啟的檔案的最後一個塊的狀態更改為 UNDER_CONSTRUCTION 狀態,並將所有其他塊的狀態更改為 COMPLETE。
副本和塊的簡化狀態轉換如兩圖所示:
在上面副本/塊狀態轉換過程中,有一個重要的判斷依據,那就是 Generation Stamp(GS)
GS 是由 NameNode 持久維護的每個塊的單調遞增的 8 位元組數。塊和副本的 GS 主要的作用是以下:
- 檢測塊的陳舊副本:即,當副本 GS 比塊 GS 舊時,例如,在副本中以某種方式跳過 append 操作時,可能會發生這種情況。
- 檢測 DataNode 上的過期副本,比如 datanode 死了很長時間後重新加入叢集。
當發生以下任何一種情況時,需要生成一個新的 GS:
- 建立了一個新檔案
- 客戶端開啟現有檔案以進行 append 或 truncate
- 客戶端在向 DataNode(s) 寫入資料時遇到錯誤並請求新的 GS
- NameNode 啟動檔案的租約恢復
接下來,我們來看租約恢復,塊恢復是由租約恢復觸發,並且包含在租約恢復過程中的.
租約恢復過程是在 NameNode 上觸發的.觸發的場景有如下兩個:當監控執行緒監控到租約 hard limit 到期時,或者一個客戶端在 soft limit到期時嘗試從另一個客戶端接管租約時。租約恢復會檢查由同一客戶端寫入的每個開啟檔案,如果檔案的最後一個塊不處於 COMPLETE 狀態,則對檔案執行塊恢復,然後關閉檔案。
下面是給定檔案 f 的租約恢復過程。當客戶端異常死亡時,這個客戶端寫入而開啟的每個檔案也會發生如下過程:
- 得到 包含 f 的最後一個塊的 DataNode。
- 將其中一個 DataNode 指定為主 DataNode p。
- p 從 NameNode 獲取新的 GS 標記。
- p 從每個 DataNode 獲取這個塊的資訊。
- p 計算得到這個塊的最小長度。
- p 更新具有合法 GS 標記的 DataNode 的塊, 讓其更新為新的 GS 標記和最小塊的長度。
- p 通知 NameNode 更新的結果。
- NameNode 更新 BlockInfo。
- NameNode 刪除 f 的租約(其他寫入者現在可以獲得寫入 f 的租約)。
- NameNode 向 edit log 提交更改。
其中步驟 3 到 7 是恢復過程中的塊恢復部分。
有時,需要在硬限制到期之前強制恢復檔案的租約。為此,可以使用命令強制恢復租約:
hdfs debug recoverLease [-path] [-retries ]
由內到外,接下來,繼續看外層的管道恢復 (pipeline recovery)
首先看寫入管道(write pipeline)的流程
當 HDFS 客戶端寫入檔案時,資料將作為順序塊寫入。為了寫入或構造一個塊,HDFS 將塊分成 packets(實際上不是網路資料包,而是訊息;packets 實際是指帶著這些訊息的類),並將它們傳遞到寫入管道中的每個 DataNode,如下圖:
寫流水線分為三個階段:
- 管道啟動。客戶端沿管道傳送 Write_Block 請求,最後一個 DataNode 傳送回確認。收到確認後,管道準備好寫入。
- 資料流。資料透過管道以資料包的形式傳送。客戶端快取資料,直到一個packet 資料包被填滿,然後將資料包傳送到管道。如果客戶端呼叫 hflush(),那麼即使一個資料包沒有滿,它仍然會被髮送到管道並且必須得收到前一個資料包 hflush() 的確認。
- 關閉(finalize 副本並關閉管道)。客戶端等待直到所有資料包都被確認,然後傳送關閉請求。管道中的所有 DataNode 將相應的副本更改為 FINALIZED 狀態並報告回 NameNode。如果配置的最小副本數量的 DataNode 報告了其相應副本的 FINALIZED 狀態,則 NameNode 然後將塊的狀態更改為 COMPLETE。
當管道中的一個或多個 DataNode 在寫入塊的三個階段中的任何一箇中遇到錯誤時,則會啟動管道恢復。
從管道啟動失敗中恢復
- 如果管道是為一個新塊建立的,客戶端會放棄該塊並向 NameNode 請求一個新塊和一個新的 DataNode 列表。管道為新塊重新初始化。
- 如果建立管道 append 塊操作,則客戶端使用剩餘的 DataNode 重建管道並增加塊的 GS 標記。
從資料流失敗中恢復
- 當管道中的 DataNode 檢測到錯誤(例如,checksum 錯誤或寫入磁碟失敗)時,該 DataNode 透過關閉所有 TCP/IP 連線將自己從管道中取出。
- 接著客戶端檢測到故障,它會停止向管道傳送資料,並使用剩餘的 DataNode 重建新的管道。接著,該塊的所有副本都被更新到一個新的 GS。
- 客戶端使用這個新的 GS 繼續傳送資料包。如果傳送的資料已經被某些 DataNode 接收了,他們會忽略該資料包並往管道下游傳遞.
從關閉失敗中恢復
當客戶端在關閉狀態下檢測到故障時,它會使用剩餘的 DataNode 重建管道。如果副本尚未最終確定,則每個 DataNode 都會增加副本的 GS 並最終確定副本。
當一個 DataNode 壞時,它會將自己從管道中移除。在管道恢復過程中,客戶端可能需要使用剩餘的 DataNode 重建新的管道。 (它可能會也可能不會用新的 DataNode 替換壞的 DataNode,這取決於下文中配置的 DataNode 替換策略。)replication 監視器將負責複製塊以滿足配置的副本數。
失敗時 datanode 的替換策略
在使用剩餘的 DataNode 設定恢復管道時,關於是否新增額外的 DataNode 以替換壞的 DataNode 有四種可配置策略:
- DISABLE:禁用 DataNode 替換並在dn 上丟擲錯誤。
- NEVER:當管道發生故障時,永遠不替換 DataNode(通常不建議)。
- DEFAULT:根據以下條件替換:
a. 假設 r 為配置的副本數。
b. 設 n 為現已有副本資料的節點的數量。
c. 僅當 r >= 3 且滿足下面任一條件才新增新的 DataNode- flour(r/2) >= n
- r > n 並且塊是被 hflushed/appended
- ALWAYS:當現有的 DataNode 失敗時,總是新增一個新的 DataNode。如果無法替換 DataNode,則會失敗。
替換策略的開關為 dfs.client.block.write.replace-datanode-on-failure.enable ,值為 false 時,禁用所有策略.
值為 true,開啟替換策略,此時透過配置 dfs.client.block.write.replace-datanode-on-failure.policy 來指定策略,預設策略為 default
使用 default 或 always 時,如果管道中只有一個 DataNode 成功,則錯誤恢復永遠不會成功,客戶端將無法執行寫入直到超時。這種情況可以配置如下屬性來解決此問題:dfs.client.block.write.replace-datanode-on-failure.best-effort
預設為false。使用預設設定,客戶端將繼續嘗試,直到滿足指定的策略。當該屬性設定為 true 時,即使不能滿足指定的策略(例如管道中只有一個成功的 DataNode,小於策略要求),仍然允許客戶端繼續來寫。
租約恢復、塊恢復和管道恢復對於 HDFS 容錯至關重要。它們共同保證了即使存在網路和節點故障的情況下,寫入到 HDFS 中是持久且一致的,