Hadoop高可用特性解析

HDFS HA

HDFS採用的是fsimage + edits的存儲方式,fsimage是某個時間的內存文件系統鏡像,edits是修改操作,每個修改操作稱爲一個事務,有一個整形的事務id指定。checkpoint的時候就存儲一次fsimage,同時可以刪除之前的edits。另外edits切割爲很多segement,不同的segment都包含一段修改操作記錄,正在寫入的segment的文件名有inprogress和起始結束的txid修飾,寫入完成就只有起始結束的txid修飾。recover的時候先把最新的fsimage加載到內存,然後回放對應的edits文件,這樣就恢復到最新狀態。整個流程類似於數據庫的redo log。

HDFS爲了提高可用性,提供了SecondaryNameNode,NFS,QuorumJournalManager等幾種方法。

  • SecondaryNameNode

SecondaryNameNode定期從NameNode拉取fsimage和edits文件,NameNode停止寫入edit,寫入新的edit.new。SecondaryNameNode然後加載fsimage,合併edits之後生成新的fsimage2,把fsimage2複製到NameNode中,NameNode替換原來的edits和fsimage。然後把edit.new改名回edit。

可以手動從SecondaryNameNode恢復之前checkpoint的數據。

作用就是幫NameNode進行check point。沒有failover。

  • Using the NFS

熱備。Active NameNode和StandBy NameNode之間有共享的存儲NFS。Active會把修改寫入NFS的edit,StandBy會看到這個修改,然後應用到自身的namespace,通過這樣保持與Active的同步。

DataNode需要向兩個NameNode發送Block信息。

爲了避免split brain,需要設置fencing方法,當failover的時候,禁止之前的Active繼續向NFS寫入edit。

  • Using the Quorum Journal Manager
基於Paxos的Quorum-based方法。
整個流程和Using the NFS類似,使用JournalNode作爲共享存儲。
包含以下模塊:
1. NameNode中運行的QuorumJournalManager。負責通過RPC和JournalNode通信,發送edits,執行fencing和同步等操作。分爲Active NN和StandBy NN兩種狀態的NN。
2. JournalNode守護進程運行在N(N>=3)臺機器上。每個進程都允許QuourumJournalManager寫入修改到本地中。
其中fsimage保存NameNode的本地,JN只負責存儲edits內容。
另外算法必須保證
1.任何同步的修改不能lost;
2.任何沒有同步的修改可能lost也可能生效;
3.如果edit被讀取,那麼就一定不能lost;
4.對於指定的txid,必須只有一個有效的事務。

Active NN正常運行時,流程如下:

往JournalNode寫入Edits(QJournalProtocol.journal)

NameNode進行logSync時,QJM把隊列的batch edits的byte數組複製到新的數組後發送到所有的JoualNode中。
JN收到edits後,需要進行一些校驗:
(a)校驗epoch number是否大於等於lastPromisedEpoch (b)校驗epoch number是否等於lastWriterEpoch (c)校驗請求的segmentTxID是否等於當前log segment的txID  (d)校驗batch edits的firstTxnId是否等於上一次寫入Edits的transactionsID + 1(nextTxId) 
其中a是fencing機制,避免之前active的NN進行修改,如果大於lastPromisedEpoch,則更新並保存到本地;b是保證lastWriterEpoch的正確,用於recover;c是由於每個log segment都有txid,保證每個segment的內容一致;d是保證segment的edits是按照txid有序,也是保證內容一致;
校驗之後,把edits寫入當前log segment,然後返回success。如果JN的校驗失敗,會拋出OutOfSync或者IO異常,NN會標記這個JN停止後續對其發送RPC修改,直到startLogSegment才重新寫入該JN。
NameNode在執行logSync的線程中,等待quorum節點數返回。如果quorum節點數返回異常或者超時,logSync()跑出異常。這樣QJM就會導致NameNode退出。

注意:並不保證正在寫的log segment狀態一致。

Finalize log segment (QJournalProtocol.finalizeLogSegment)

NameNode發現log segment的txid數量超過閾值等需要roll edit log的時候,就需要進行Finalize log segment操作。同時,由於確保所有finalize的log segment的內容必須一致,並且不能被修改,因此也是log synchronize的操作。 
首先令第一個寫log segment的txid作爲該log segment的txid,發送該txid以及最後一次寫的txid作爲參數,RPC請求所有的JN進行finalizeLogSegment。
JN收到請求後,同樣進行(a),(b)的校驗,然後如果當前log segment的txid等於請求的txid,並且最後一次寫的txid也相等,則重命名當前的in progress的log segment,表示完成finalize。並且同時刪除之前paxos記錄的對應文件?
NameNode等待quorum節點數返回。

注意:保證finalize後的log segment是不變的且存在quorum節點數中其內容保證一致。因此,StandBy NN只會定期讀取quorum節點數的finalize log segment,應用到自身的鏡像中,其狀態會有一定延遲。

Start new log segment (QJournalProtocol.startLogSegment)

roll edit log完成finalize log segment之後就需要start new log segment。注意如果JN是OutOfSync狀態,此RPC會發送。
NameNode把上一次寫的txid + 1作爲log segment txid參數發送RPC到所有的JN中。
JN收到請求後,同樣進行(a),(b)的校驗,如果當前segment沒有被finalize,放棄當前segment。然後更新lastWriterEpoch爲當前epoch。最後創建新的log segment。

NameNode等待quorum節點數返回。


Active NN  log recover流程如下:

如果Active NameNode掛掉,則StandBy NameNode收到transitionToActive的RPC請求後,首先要重新創建所有分別連接JN的IPCLoggerChannel,準備寫入edits,然後需要fencing old active namenode

Fencing old Active NameNode

HDFS的fencing機制依靠的是epoch number。NameNode變爲active的時候,都會創建一個唯一的嚴格遞增epoch number,因此擁有更大的epoch number表示當前的NameNode是最近的Active NameNode。NameNode請求JN修改edits時,都會帶上自己的epoch number作爲參數,JN會把這個參數與自己保存的lastPromisedEpoch比較,小於則是old active NN的請求,則拒絕;等於則接收修改,大於則接受修改並更新當前值保存到disk。
創建epoch number(createNewUniqueEpoch)算法如下:
* QJM發送getJournalState到所有的JN中。JN返回lastPromisedEpoch。QJM取quorum節點數返回的最大值,然後把這個最大值 + 1作爲參數;

* QJM發送newEpoch到所有的JN中,JN把參數與lastPromisedEpoch比較,小於等於則返回異常,大於則更新lastPromisedEpoch,另外掃描所有的edits文件,返回最新segment的segmentTxId。QJM取quorum節點數返回的最大segmentTxId作爲後續恢復的segmentTxId。

Recovering in-progress logs

Active NN掛掉後,由於之前正在寫的log segment是無法保證一致性,因此要進行recover操作。主要是把quorum節點數上的log segment進行同步,然後finalize保證一致性,這樣StandBy NN纔可以讀取log segment恢復到最新狀態,從而成爲Active NN對外提供服務。Recover算法如下:

prepareRecovery 

QJM把newEpoch選取的segmentTxId作爲恢復參數發送prepareRecovery到所有JN中,JN進行(a)(b)校驗,首先查看是否存在之前acceptRecovery中保存的paxosData,如果存在則嘗試把http下載的臨時文件替換inprogress的log segment。然後查看是否存在segmentTxId對應的log segment,存在則返回其起始結束的txid以及是否inprogress信息;如果存在segmentTxId的恢復信息paxosData並且對應log segment沒有被finalize,則把之前信息保存的segment狀態以及epoch number作爲acceptedInEpoch返回;同時返回lastWriterEpoch以及committedTxnId。(Phase 1a, 1b in Paxos)。
QJM收到response後,需要選擇最佳JN作爲恢復源,選擇條件如下:
1. 有log segment的比沒有的更好,否則繼續比較
2. log segment被finalize的比沒有的更佳,否則繼續比較
3. 此時兩者均爲in-progress log segment。令seenEpoch爲修改過edit的最大epoch (acceptedInEpoch和lastWriteEpoch最大值),則seenEpoch值越大更佳,否則繼續比較
4. 比較log segment的結束txId。

取最佳的JN中的一個作爲恢復源

acceptRecovery 

QJM把最佳的JN的URL以及log segment state作爲參數發送acceptRecovery到所有的JN。JN進行(a)(b)校驗,然後如果不存在segmentTxId的log segment,或者該log segment的的長度要小,則通過Http下載最佳JN的log segment。下載到臨時文件後,JN會把這次recovery的數據(epoch和segmentState)作爲paxosData保存到本地,保存成功後才把臨時文件替換原來inprogress的log segment。這樣做可以在下載到替換的過程中發生崩潰後,重新recover時prepareRecovery能直接替換inprogress的log segment。(Phase 2a, 2b in Paxos)

finalizeLogSegment

QJM把上面recovery有quorum節點數同步的log segment的startTxId和endTxId作爲參數發送finalizeLogSegment到所有的JN。JN會對自身的segment進行校驗,如果txId不符合,則會放棄finalize的操作,避免不一致。finalize完成後,JN會把之前保存的paxosData刪除,這是由於finalize的log segment和paxosData都能夠正確表示recover的選擇。
recover過程完成後,StandBy NN調用EditlogTailer.catchupDuringFailover從JN上讀取edits,應用到內存的fsimage中。

注意, recover過程可能導致某些沒有同步的修改生效。

  • Paxos ?Zab!

    HDFS的edits是增量狀態修改,必須要保證其請求順序一致。但Paxos本身是state machine replication,並不強調順序一致性。事實上,HDFS的HA更類似Zookeeper的Zab協議。具體在於當主節點崩潰的時候,新的主節點必須首先執行創建epoch,prepareRecovery兩個過程,保證之前的修改在多數從節點上達到一致性。其中,創建epoch的過程類似Zab的Discovery階段,都需要多數從節點保證不接收舊epoch的請求。prepareRecovery的過程類似於Zab的Synchronization階段,都需要從之前的從節點狀態中找到最新的記錄,然後讓其它同步,只不過HDFS是更具體去考慮segment的同步。

  • Using the Quorum Journal Manager with automatic failover
雖然使用QJM的方法可以提供可靠的共享存儲,但仍然需要manual failover,爲了達到automatic failover,需要依賴另外一個自動檢測NN故障並且出觸發failover的模塊。於是,官方提出了基於Zookeeper的automatic failover的方法。
包含三個模塊:
1. HealthMonitor。定期檢測NN是否正常提供服務
2. ActiveStandbyElector。負責管理和監控ZK。
3. ZKFailoverController。接收HealthMonitor和ActiveStandbyElector的消息並且管理NN狀態。另外注意fecing處於中間狀態的NN。
目前這三個模塊在不同於NN的一個獨立的JVM中,大致如下:


HealthMonitor

HealthMonitor主要負責定期向本地的NN發送monitorHealth的RPC。NN收到monitorHealth的RPC後,會檢查是否有足夠的資源,目前是是否有足夠的硬盤空間,如果沒有則拋出異常。HealthMonitor根據NN的RPC返回決定其是否HEALTHY,然後通過callback通知ZKFC狀態的變化。

ActiveStandbyElector

ActiveStandbyElector主要負責ZK的相關操作。ZKFC主要調用其兩個接口:

joinElection

開始進行Active NN的選舉。具體是在ZK的LockFilePath異步創建臨時znode。如果ZK通知創建成功,則需要fence old Active NN。(如果使用QJM作爲共享存儲,這裏應該不需要)
fence的做法是首先同步從ZK的BreadCrumbPath獲取old Active NN的信息,以其爲target作爲參數回調ZKFC的fenceOldActive方法。ZKFC首先嚐試往target發送transitionToStandby的RPC,如果timeout或者失敗,則調用配置的fence method(例如SSH)強制kill掉old Active NN。
如果fence失敗,則sleep一段時間後再重新joinElection,目的是讓其它NN有機會becomeActive;如果fence成功後,則把local NN的信息同步寫入ZK的BreadCrumbPath,這樣failover讓其它NN對local NN進行fence。之後就回調ZKFC的becomeActive方法,ZKFC調用transitionToActive RPC通知local NN,於是就到NN的Active NN  log recover流程。接着,ActiveStandbyElector監控ZK的LockFilePath狀態,以便探知Active NN的狀態。
如果ZK通知已經創建LockFilePath,已經有其它NN獲得了lock,則回調ZKFC的becomeStandby,ZKFC調用transitionToStandby RPC通知local NN,讓其轉換爲StandBy狀態。

quitElection

退出NN選舉。具體是停止ZK的連接。這樣之前創建的LockFilePath的臨時znode就會被ZK刪掉。其它NN就會探知到,便會去創建znode。

ZKFailoverController

ZKFailoverController相對簡單,主要是初始化HealthMonitor以及ActiveStandbyElector模塊,然後等待HealthMonitor和ActiveStandbyElector的回調,作出具體的邏輯,前面已經有介紹,這裏不再贅述。可以看到,ZKFC就是相當於一個controller的角色。


ResourceManager HA

  • Write edit

state machine異步觸發事件把RM的狀態通過ZKRMStateStore保存到ZK上。

  • Automatic failover

ResourceManager的子服務AdminService實現協議HAServiceProtocol(transitionToActive,transitionToStandby)

AdminService包含EmbeddedElectorService,EmbeddedElectorService包含ActiveStandbyElector以及實現回調ActiveStandbyElector.ActiveStandbyElectorCallback。

EmbeddedElectorService在ActiveStandbyElectorCallback的方法becomeActive,becomeStandby的實現只是簡單回調AdminService的transitionToActive,transitionToStandby接口。

與HDFS類似,通過創建ZK的lockPath進行選舉,監聽lockPath實現automatic failover,調用ActiveStandbyElector負責具體的ZK操作,不同的是,RM和ActiveStandbyElector在同一個進程,並不需要RPC,直接回調方法即可。

  • Fence

EmbeddedElectorService沒有具體實現ActiveStandbyElectorCallback.fenceOldActive方法,ZKRMStateStore本身實現了fence機制。具體如下:

ZKRMStateStore初始化時,在rootPath的znode上建立一個CREATE_DELETE_PERMS權限的ACL,以localAddress:port爲username,這樣就保證只有本地RM修改rootPath的znode。每次寫入數據時,使用ZK的multi API一次執行三個ZK操作:首先在rootPath創建fencingNodePath的childNode,然後再寫入數據,最後再刪除fencingNodePath。如果創建fencingNodePath失敗,則證明已經有已經有其它RM創建了ACL,防止了split-brain。在這裏,fencingNodePath相當於HDFS HA的epoch number。

  • Recover

transitionToActive從ZK恢復所有數據,包括Delegation token, Application state, AMRMToken等。然後從這些數據中恢復所有相關服務類。

transitionToStandby則進行簡單的初始化,包括監聽ZK的lockPath。


HDFS HA vs ResourceManager HA

事實上,EmbeddedElectorService的作用就相當於HDFS的ZKFC deamon。對比一下HDFS的ZKFC和RM的EmbeddedElectorService的設計。

HDFS故意分離ZKFC的目的:

  • avoid failover in the case of GC (since ZKFC has a very low heap requirement) but still failover fast in machine failure.
  • avoid adding any dependency on ZK within the NN
  • allow the option to use other resource managers
    • in practice no one has done this and I think the extra complexity all of our pluggability introduces is not worth it

對比HDFS,RM內存壓力沒有那麼大,並且RM使用ZKRMStateStore本身就有對ZK的依賴,合併在RM令實現更加簡單,並且減輕了部署難度,因此對於RM來說,這是更佳的設計。


Reference

https://issues.apache.org/jira/secure/attachment/12547598/qjournal-design.pdf
https://issues.apache.org/jira/secure/attachment/12521279/zkfc-design.pdf

https://www.ibm.com/developerworks/cn/opensource/os-cn-hadoop-name-node

https://issues.apache.org/jira/browse/YARN-1029

https://issues.apache.org/jira/browse/YARN-1222


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章