在Hadoop集羣整個生命週期裏,因爲調整參數、Patch、升級等多種場景須要頻繁操做NameNode重啓,不論採用何種架構,重啓期間集羣總體存在可用性和可靠性的風險,因此優化NameNode重啓很是關鍵。html
本文基於Hadoop-2.x和HA with QJM社區架構和系統設計(如圖1所示),經過梳理NameNode重啓流程,並在此基礎上,闡述對NameNode重啓優化實踐。node
在HDFS的整個運行期裏,全部元數據均在NameNode的內存集中管理,可是因爲內存易失特性,一旦出現進程退出、宕機等異常狀況,全部元數據都會丟失,給整個系統的數據安全會形成不可恢復的災難。爲了更好的容錯能力,NameNode會週期進行CheckPoint,將其中的一部分元數據(文件系統的目錄樹Namespace)刷到持久化設備上,即二進制文件FSImage,這樣的話即便NameNode出現異常也能從持久化設備上恢復元數據,保證了數據的安全可靠。git
可是僅週期進行CheckPoint仍然沒法保證全部數據的可靠,如前次CheckPoint以後寫入的數據依然存在丟失的問題,因此將兩次CheckPoint之間對Namespace寫操做實時寫入EditLog文件,經過這種方式能夠保證HDFS元數據的絕對安全可靠。github
事實上,除Namespace外,NameNode還管理很是重要的元數據BlocksMap,描述數據塊Block與DataNode節點之間的對應關係。NameNode並無對這部分元數據一樣操做持久化,緣由是每一個DataNode已經持有屬於本身管理的Block集合,將全部DataNode的Block集合彙總後便可構造出完整BlocksMap。apache
HA with QJM架構下,NameNode的整個重啓過程當中始終以SBN(StandbyNameNode)角色完成。與前述流程對應,啓動過程分如下幾個階段:緩存
默認狀況下,NameNode會保存兩個FSImage文件,與此對應,也會保存對應兩次CheckPoint以後的全部EditLog文件。通常來講,NameNode重啓後,經過對FSImage文件名稱判斷,選擇加載最新的FSImage文件及回放該CheckPoint以後生成的全部EditLog,完成後根據加載的EditLog中操做條目數及距上次CheckPoint時間間隔(後續詳述)肯定是否須要執行CheckPoint,以後進入等待全部DataNode註冊和元數據彙報階段,當這部分數據收集完成後,NameNode的重啓流程結束。安全
從線上NameNode歷次重啓時間數據看,各階段耗時佔比基本接近如圖2所示。微信
通過優化,在元數據總量540M(目錄樹240M,數據塊300M),超過4K規模的集羣上重啓NameNode總時間~35min,其中加載FSImage耗時~15min,秒級回放EditLog,數據塊彙報耗時~20min,基本可以知足生產環境的需求。數據結構
如前述,FSImage文件記錄了HDFS整個目錄樹Namespace相關的元數據。從Hadoop-2.4.0起,FSImage開始採用Google Protobuf編碼格式描述(HDFS-5698),詳細描述文件見fsimage.proto。根據描述文件和實現邏輯,FSImage文件格式如圖3所示。架構
從fsimage.proto和FSImage文件存儲格式容易看到,除了必要的文件頭部校驗(MAGIC)和尾部文件索引(FILESUMMARY)外,主要包含如下核心數據:
NameNode執行CheckPoint時,遵循Protobuf定義及上述文件格式描述,重啓加載FSImage時,一樣按照Protobuf定義的格式從文件流中讀出相應數據構建整個目錄樹Namespace及其餘元數據。將FSImage文件從持久化設備加載到內存並構建出目錄樹結構後,實際上並無徹底恢復元數據到最新狀態,由於每次CheckPoint以後還可能存在大量HDFS寫操做。
NameNode在響應客戶端的寫請求前,會首先更新內存相關元數據,而後再把這些操做記錄在EditLog文件中,能夠看到內存狀態實際上要比EditLog數據更及時。
記錄在EditLog之中的每一個操做又稱爲一個事務,對應一個整數形式的事務編號。在當前實現中多個事務組成一個Segment,生成獨立的EditLog文件,其中文件名稱標記了起止的事務編號,正在寫入的EditLog文件僅標記起始事務編號。EditLog文件的格式很是簡單,沒再經過Google Protobuf描述,文件格式如圖4所示。
一個完整的EditLog文件包括四個部份內容,分別是:
NameNode加載FSImage完成後,即開始對該FSImage文件以後(經過比較FSImage文件名稱中包含的事務編號與EditLog文件名稱的起始事務編號大小肯定)生成的全部EditLog嚴格按照事務編號從小到大逐個遵循上述的格式進行每個HDFS寫操做事務回放。
NameNode加載完全部必需的EditLog文件數據後,內存中的目錄樹即恢復到了最新狀態。
通過前面兩個步驟,主要的元數據被構建,HDFS的整個目錄樹被完整創建,可是並無掌握數據塊Block與DataNode之間的對應關係BlocksMap,甚至對DataNode的狀況都不掌握,因此須要等待DataNode註冊,並完成對從DataNode彙報上來的數據塊彙總。待彙總的數據量達到預設比例(dfs.namenode.safemode.threshold-pct)後退出Safemode。
NameNode重啓通過加載FSImage和回放EditLog後,全部DataNode無論進程是否發生太重啓,都必須通過如下兩個步驟:
對於節點規模較大和元數據量較大的集羣,這個階段的耗時會很是可觀。主要有三點緣由:
以前咱們在NameNode內存全景一文中詳細描述過Block在NameNode元數據中的關鍵做用及與Namespace/DataNode/BlocksMap的複雜關係,從中也能夠看出,每一個新增Block須要維護多個關係,更況且重啓過程當中全部Block都須要創建一樣複雜關係,因此耗時相對較高。
根據前面對NameNode重啓過程的簡單梳理,在各個階段能夠適當的實施優化以加快NameNode重啓過程。
Fix:2.7.0
Hadoop-2.7.0版本前,SBN(StandbyNameNode)在執行CheckPoint操做前會先得到全局讀寫鎖fsLock,在此期間,BlockReport請求因爲不能得到全局寫鎖會持續處於等待狀態,直到CheckPoint完成後釋放了fsLock鎖後才能繼續。NameNode重啓的第三個階段,一樣存在這種狀況。並且對於規模較大的集羣,每次CheckPoint時間在分鐘級別,對整個重啓過程影響很是大。實際上,CheckPoint是對目錄樹的持久化操做,並不涉及BlocksMap數據結構,因此CheckPoint期間是可讓BlockReport請求直接經過,這樣能夠節省期間BlockReport排隊等待帶來的時間開銷,HDFS-7097正是將鎖粒度放小解決了CheckPoint過程不能處理BlockReport類型RPC請求的問題。
與HDFS-7097相對,另外一種思路也值得借鑑,就是重啓過程儘量避免出現CheckPoint。觸發CheckPoint有兩種狀況:時間週期或HDFS寫操做事務數,分別經過參數dfs.namenode.checkpoint.period和dfs.namenode.checkpoint.txns控制,默認值分別是3600s和1,000,000,即默認狀況下一個小時或者寫操做的事務數超過1,000,000觸發一次CheckPoint。爲了不在重啓過程當中頻繁執行CheckPoint,能夠適當調大dfs.namenode.checkpoint.txns,建議值10,000,000 ~ 20,000,000,帶來的影響是EditLog文件累計的個數會稍有增長。從實踐經驗上看,對一個有億級別元數據量的NameNode,回放一個EditLog文件(默認1,000,000寫操做事務)時間在秒級,可是執行一次CheckPoint時間一般在分鐘級別,綜合權衡減小CheckPoint次數和增長EditLog文件數收益比較明顯。
Fix:2.8.0
ANN(ActiveNameNode)將HDFS寫操做實時寫入JN的EditLog文件,爲同步數據,SBN默認間隔1min從JN拉取一次EditLog文件並進行回放,完成後執行全局Quota檢查和計算,當Namespace規模變大後,全局計算和檢查Quota會很是耗時,在此期間,整個SBN的Namenode進程會被Hang住,以致於包括DN心跳和BlockReport在內的全部RPC請求都不能及時處理。NameNode重啓過程當中這個問題影響突出。
實際上,SBN在EditLog Tailer階段計算和檢查Quota徹底沒有必要,HDFS-6763將這段處理邏輯後移到主從切換時進行,解決SBN進程間隔1min被Hang住的問題。
從優化效果上看,對一個擁有接近五億元數據量,其中兩億數據塊的NameNode,優化前數據塊彙報階段耗時~30min,其中觸發超過20次因爲計算和檢查Quota致使進程Hang住~20s的狀況,整個BlockReport階段存在超過5min無效時間開銷,優化後可到~25min。
Fix:2.7.1
NameNode加載完元數據後,全部DataNode嘗試開始進行數據塊彙報,若是彙報的數據塊相關元數據尚未加載,先暫存消息隊列,當NameNode完成加載相關元數據後,再處理該消息隊列。對第一次塊彙報的處理比較特別(NameNode重啓後,全部DataNode的BlockReport都會被標記成首次數據塊彙報),爲提升處理速度,僅驗證塊是否損壞,以後判斷塊狀態是否爲FINALIZED,如果創建數據塊與DataNode的映射關係,創建與目錄樹中文件的關聯關係,其餘信息一律暫不處理。對於非初次數據塊彙報,處理邏輯要複雜不少,對報告的每一個數據塊,不只檢查是否損壞,是否爲FINALIZED狀態,還會檢查是否無效,是否須要刪除,是否爲UC狀態等等;驗證經過後創建數據塊與DataNode的映射關係,創建與目錄樹中文件的關聯關係。
初次數據塊彙報的處理邏輯獨立出來,主要緣由有兩方面:
啓動過程當中,不提供正常讀寫服務,因此只要確保正常數據(整個Namespace和全部FINALIZED狀態Blocks)無誤,無效和冗餘數據處理徹底能夠延後到IBR(IncrementalBlockReport)或下次BR(BlockReport)。
這原本是很是合理和正常的設計邏輯,可是實現時NameNode在判斷是否爲首次數據塊塊彙報的邏輯一直存在問題,致使這段很是好的改進點邏輯實際上長期並未真正執行到,直到HDFS-7980在Hadoop-2.7.1修復該問題。HDFS-7980的優化效果很是明顯,測試顯示,對含80K Blocks的BlockReport RPC請求的處理時間從~500ms可優化到~100ms,從重啓期整個BlockReport階段看,在超過600M元數據,其中300M數據塊的NameNode顯示該階段從~50min優化到~25min。
Fix:2.7.0
若NameNode重啓前產生過大刪除操做,當NameNode加載完FSImage並回放了全部EditLog構建起最新目錄樹結構後,在處理DataNode的BlockReport時,會發現有大量Block不屬於任何文件,Hadoop-2.7.0版本前,對於這類狀況的輸出日誌邏輯在全局鎖內,因爲存在大量IO操做的耗時,會嚴重拉長處理BlockReport的處理時間,影響NameNode重啓時間。HDFS-7503的解決辦法很是簡單,把日誌輸出邏輯移出全局鎖外。線上效果上看對同類場景優化比較明顯,不過若是重啓前不觸發大的刪除操做影響不大。
選擇HA熱備方案SBN(StandbyNameNode)仍是冷備方案SNN(SecondaryNameNode)架構,執行CheckPoint的邏輯幾乎一致,如圖6所示。若是SBN/SNN服務長時間未正常運行,CheckPoint不能按照預期執行,這樣會積壓大量EditLog。積壓的EditLog文件越多,重啓NameNode須要加載EditLog時間越長。因此儘量避免出現SNN/SBN長時間未正常服務的狀態。
在一個有500M元數據的NameNode上測試加載一個200K次HDFS事務操做的EditLog文件耗時~5s,按照默認2min的EditLog滾動週期,若是一週時間SBN/SNN未能正常工做,則會累積~5K個EditLog文件,此後一旦發生NameNode重啓,僅加載EditLog文件的時間就須要~7h,也就是整個集羣存在超過7h不可用風險,因此切記要保證SBN/SNN不能長時間故障。
當集羣中大量數據塊的實際存儲副本個數超過副本數時(跨機房架構下這種狀況比較常見),NameNode重啓後會迅速填充到PostponedMisreplicatedBlocks,直到相關數據塊所在的全部DataNode彙報完成且退出Stale狀態後才能被清理。若是PostponedMisreplicatedBlocks數據量較大,每次全遍歷須要消耗大量時間,且整個過程也要持有全局鎖,嚴重影響處理BlockReport的性能,HDFS-6425和HDFS-6772分別將可能在BlockReport邏輯內部遍歷很是大的數據結構PostponedMisreplicatedBlocks優化到異步執行,並在NameNode重啓後讓DataNode快速退出blockContentsStale狀態避免PostponedMisreplicatedBlocks過大入手優化重啓效率。
NameNode處理BlockReport的效率低主要緣由仍是每次BlockReport所帶的Block規模過大形成,因此能夠經過調整Block數量閾值,將一次BlockReport分紅多盤分別彙報,以提升NameNode對BlockReport的處理效率。可參考的參數爲:dfs.blockreport.split.threshold,默認值1,000,000,即當DataNode本地的Block個數超過1,000,000時纔會分盤進行彙報,建議將該參數適當調小,具體數值可結合NameNode的處理BlockReport時間及集羣中全部DataNode管理的Block量分佈肯定。
前面提到NameNode彙總DataNode上報的數據塊量達到預設比例(dfs.namenode.safemode.threshold-pct)後就會退出Safemode,通常狀況下,當NameNode退出Safemode後,咱們認爲已經具有提供正常服務的條件。可是對規模較大的集羣,按照這種默認策略及時執行主從切換後,容易出現短期丟塊的問題。考慮在200M數據塊的集羣,默認配置項dfs.namenode.safemode.threshold-pct=0.999,也就是當NameNode收集到200M*0.999=199.8M數據塊後便可退出Safemode,此時實際上還有200K數據塊沒有上報,若是強行執行主從切換,會出現大量的丟塊問題,直到數據塊彙報完成。應對的辦法比較簡單,嘗試調大dfs.namenode.safemode.threshold-pct到1,這樣只有全部數據塊上報後纔會退出Safemode。可是這種辦法同樣不能保證萬無一失,若是啓動過程當中有DataNode彙報完數據塊後進程掛掉,一樣存在短期丟失數據的問題,由於NameNode彙總上報數據塊時並不檢查副本數,因此更穩妥的解決辦法是利用主從NameNode的JMX數據對比全部DataNode當前彙報數據塊量的差別,當差別都較小後再執行主從切換能夠保證不發生上述問題。
除了優化NameNode重啓時間,實際運維中還會遇到須要滾動重啓集羣全部節點或者一次性重啓整集羣的狀況,不恰當的重啓方式也會嚴重影響服務的恢復時間,因此合理控制重啓的節奏或選擇合適的重啓方式尤其關鍵,HDFS集羣啓動方式分析一文對集羣重啓方式進行了詳細的闡述,這裏就再也不展開。
通過屢次優化調整,從線上NameNode歷次的重啓時間監控指標上看,收益很是明顯,圖7截取了其中幾回NameNode重啓時元數據量及重啓時間開銷對比,圖中直觀顯示在500M元數據量級下,重啓時間從~4000s優化到~2000s。
這裏羅列了一小部分實踐過程當中能夠有效優化重啓NameNode時間或者重啓全集羣的點,其中包括了社區成熟Patch和相關參數優化,雖然實現邏輯都很小,可是實踐收益很是明顯。固然除了上述提到,NameNode重啓還有不少能夠優化的地方,好比優化FSImage格式,並行加載等等,社區也在持續關注和優化,部分討論的思路也值得關注、借鑑和參考。
NameNode重啓甚至全集羣重啓在整個Hadoop集羣的生命週期內是比較頻繁的運維操做,優化重啓時間能夠極大提高運維效率,避免可能存在的風險。本文經過分析NameNode啓動流程,並結合實踐過程簡單羅列了幾個供參考的有效優化點,藉此但願能給實踐過程提供可優化的方向和思路。
小橋,美團點評技術工程部數據平臺研發工程師。2012年北京航空航天大學畢業,2015年初加入美團點評,關注Hadoop生態存儲方向,致力於爲美團點評提供穩定、高效、易用的離線數據存儲服務。
回答「思考題」、發現文章有錯誤、對內容有疑問,均可以來微信公衆號(美團點評技術團隊)後臺給咱們留言。咱們每週會挑選出一位「優秀回答者」,贈送一份精美的小禮品。快來掃碼關注咱們吧!