原文轉自:https://tech.meituan.com/namenode.html 感謝原做者html
從整個HDFS系統架構上看,NameNode是其中最重要、最複雜也是最容易出現問題的地方,並且一旦NameNode出現故障,整個Hadoop集羣就將處於不可服務的狀態,同時隨着數據規模和集羣規模地持續增加,不少小量級時被隱藏的問題逐漸暴露出來。因此,從更高層次掌握NameNode的內部結構和運行機制尤爲重要。除特別說明外,本文基於社區版本Hadoop-2.4.1[1][2],雖然2.4.1以後已經有屢次版本迭代,可是基本原理相同。node
NameNode管理着整個HDFS文件系統的元數據。從架構設計上看,元數據大體分紅兩個層次:git
如圖1所示[1]。Namespace管理的元數據除內存常駐外,也會週期Flush到持久化設備上FsImage文件;BlocksMap元數據只在內存中存在;當NameNode發生重啓,首先從持久化設備中讀取FsImage構建Namespace,以後根據DataNode的彙報信息從新構造BlocksMap。這兩部分數據結構是佔據了NameNode大部分JVM Heap空間。github
除了對文件系統自己元數據的管理以外,NameNode還須要維護整個集羣的機架及DataNode的信息、Lease管理以及集中式緩存引入的緩存管理等等。這幾部分數據結構空間佔用相對固定,且佔用較小。算法
測試數據顯示,Namespace目錄和文件總量到2億,數據塊總量到3億後,常駐內存使用量超過90GB。apache
如前述,NameNode整個內存結構大體能夠分紅四大部分:Namespace、BlocksMap、NetworkTopology及其它,圖2爲各數據結構內存邏輯分佈圖示。數組
Namespace:維護整個文件系統的目錄樹結構及目錄樹上的狀態變化;
BlockManager:維護整個文件系統中與數據塊相關的信息及數據塊的狀態變化;
NetworkTopology:維護機架拓撲及DataNode信息,機架感知的基礎;
其它:
LeaseManager:讀寫的互斥同步就是靠Lease實現,支持HDFS的Write-Once-Read-Many的核心數據結構;
CacheManager:Hadoop 2.3.0引入的集中式緩存新特性,支持集中式緩存的管理,實現memory-locality提高讀性能;
SnapshotManager:Hadoop 2.1.0引入的Snapshot新特性,用於數據備份、回滾,以防止因用戶誤操做致使集羣出現數據問題;
DelegationTokenSecretManager:管理HDFS的安全訪問;
另外還有臨時數據信息、統計信息metrics等等。緩存
NameNode常駐內存主要被Namespace和BlockManager使用,兩者使用佔比分別接近50%。其它部份內存開銷較小且相對固定,與Namespace和BlockManager相比基本能夠忽略。安全
與單機文件系統類似,HDFS對文件系統的目錄結構也是按照樹狀結構維護,Namespace保存了目錄樹及每一個目錄/文件節點的屬性。除在內存常駐外,這部分數據會按期flush到持久化設備上,生成一個新的FsImage文件,方便NameNode發生重啓時,從FsImage及時恢復整個Namespace。圖3所示爲Namespace內存結構。前述集羣中目錄和文件總量即整個Namespace目錄樹中包含的節點總數,可見Namespace自己實際上是一棵很是巨大的樹。數據結構
在整個Namespace目錄樹中存在兩種不一樣類型的INode數據結構:INodeDirectory和INodeFile。其中INodeDirectory標識的是目錄樹中的目錄,INodeFile標識的是目錄樹中的文件。因爲兩者均繼承自INode,因此具有大部分相同的公共信息INodeWithAdditionalFields,除經常使用基礎屬性外,其中還提供了擴展屬性features,如Quota、Snapshot等均經過Feature增長,若是之後出現新屬性也可經過Feature方便擴展。不一樣的是,INodeFile特有的標識副本數和數據塊大小組合的header(2.6.1以後又新增了標識存儲策略ID的信息)及該文件包含的有序Blocks數組;INodeDirectory則特有子節點的列表children。這裏須要特別說明children是默認大小爲5的ArrayList,按照子節點name有序存儲,雖然在插入時會損失一部分寫性能,可是能夠方便後續快速二分查找提升讀性能,對通常存儲系統,讀操做比寫操做佔比要高。具體的繼承關係見圖4所示。
BlocksMap在NameNode內存空間佔據很大比例,由BlockManager統一管理,相比Namespace,BlockManager管理的這部分數據要複雜的多。Namespace與BlockManager之間經過前面提到的INodeFile有序Blocks數組關聯到一塊兒。圖5所示BlockManager管理的內存結構。
每個INodeFile都會包含數量不等的Block,具體數量由文件大小及每個Block大小(默認爲64M)比值決定,這些Block按照所在文件的前後順序組成BlockInfo數組,如圖5所示的BlockInfo[A~K],BlockInfo維護的是Block的元數據,結構如圖6所示,數據自己是由DataNode管理,因此BlockInfo須要包含實際數據到底由哪些DataNode管理的信息,這裏的核心是名爲triplets的Object數組,大小爲3*replicas,其中replicas是Block副本數量。triplets包含的信息:
其中i表示的是Block的第i個副本,i取值[0,replicas)。
從前面描述能夠看到BlockInfo幾塊重要信息:文件包含了哪些Block,這些Block分別被實際存儲在哪些DataNode上,DataNode上全部Block先後鏈表關係。
若是從信息完整度來看,以上數據足夠支持全部關於HDFS文件系統的正常操做,但還存在一個使用場景較多的問題:不能經過blockid快速定位Block,因此引入了BlocksMap。
BlocksMap底層經過LightWeightGSet實現,本質是一個鏈式解決衝突的哈希表。爲了不rehash過程帶來的性能開銷,初始化時,索引空間直接給到了整個JVM可用內存的2%,而且再也不變化。集羣啓動過程,DataNode會進行BR(BlockReport),根據BR的每個Block計算其HashCode,以後將對應的BlockInfo插入到相應位置逐漸構建起來巨大的BlocksMap。前面在INodeFile裏也提到的BlockInfo集合,若是咱們將BlocksMap裏的BlockInfo與全部INodeFile裏的BlockInfo分別收集起來,能夠發現兩個集合徹底相同,事實上BlocksMap裏全部的BlockInfo就是INodeFile中對應BlockInfo的引用;經過Block查找對應BlockInfo時,也是先對Block計算HashCode,根據結果快速定位到對應的BlockInfo信息。至此涉及到HDFS文件系統自己元數據的問題基本上已經解決了。
前面提到部分都屬於靜態數據部分,NameNode內存中全部數據都要隨讀寫狀況發生變化,BlockManager固然也須要管理這部分動態數據。主要是當Block發生變化不符合預期時須要及時調整Blocks的分佈。這裏涉及幾個核心的數據結構:
excessReplicateMap:若某個Block實際存儲的副本數多於預設副本數,這時候須要刪除多餘副本,這裏多餘副本會被置於excessReplicateMap中。excessReplicateMap是從DataNode的StorageID到Block集合的映射集。
neededReplications:若某個Block實際存儲的副本數少於預設副本數,這時候須要補充缺乏副本,這裏哪些Block缺乏多少個副本都統一存在neededReplications裏,本質上neededReplications是一個優先級隊列,缺乏副本數越多的Block以後越會被優先處理。
invalidateBlocks:若某個Block即將被刪除,會被置於invalidateBlocks中。invalidateBlocks是從DataNode的StorageID到Block集合的映射集。如某個文件被客戶端執行了刪除操做,該文件所屬的全部Block會先被置於invalidateBlocks中。
corruptReplicas:有些場景Block因爲時間戳/長度不匹配等等形成Block不可用,會被暫存在corruptReplicas中,以後再作處理。
前面幾個涉及到Block分佈狀況動態變化的核心數據結構,這裏的數據其實是過渡性質的,BlockManager內部的ReplicationMonitor線程(圖5標識Thread/Monitor)會持續從其中取出數據並經過邏輯處理後分發給具體的DatanodeDescriptor對應數據結構(3.3 NetworkTopology裏會有簡單介紹),當對應DataNode的心跳過來以後,NameNode會遍歷DatanodeDescriptor裏暫存的數據,將其轉換成對應指令返回給DataNode,DataNode收到任務並執行完成後再反饋回NameNode,以後DatanodeDescriptor裏對應信息被清除。如BlockB預設副本數爲3,因爲某種緣由實際副本變成4(如以前下線的DataNode D從新上線,其中B正好有BlockB的一個副本數據),BlockManager能及時發現副本變化,並將多餘的DataNode D上BlockB副本放置到excessReplicateMap中,ReplicationMonitor線程按期檢查時發現excessReplicateMap中數據後將其移到DataNode D對應DatanodeDescriptor中invalidateBlocks裏,當DataNode D下次心跳過來後,隨心跳返回刪除Block B的指令,DataNode D收到指令實際刪除其上的Block B數據並反饋回NameNode,此後BlockManager將DataNode D上的Block B從內存中清除,至此Block B的副本符合預期,整個流程如圖7所示。
前面屢次提到Block與DataNode之間的關聯關係,事實上NameNode確實還須要管理全部DataNode,不只如此,因爲數據寫入前須要肯定數據塊寫入位置,NameNode還維護着整個機架拓撲NetworkTopology。圖8所示內存中機架拓撲圖。
從圖8能夠看出這裏包含兩個部分:機架拓撲結構NetworkTopology和DataNode節點信息。其中樹狀的機架拓撲是根據機架感知(通常都是外部腳本計算獲得)在集羣啓動完成後創建起來,整個機架的拓撲結構在NameNode的生命週期內通常不會發生變化;另外一部分是比較關鍵的DataNode信息,BlockManager已經提到每個DataNode上的Blocks集合都會造成一個雙向鏈表,更準確的應該是DataNode的每個存儲單元DatanodeStorageInfo上的全部Blocks集合會造成一個雙向鏈表,這個鏈表的入口就是機架拓撲結構葉子節點即DataNode管理的DatanodeStorageInfo。此外因爲上層應用對數據的增刪查隨時發生變化,隨之DatanodeStorageInfo上的Blocks也會動態變化,因此NetworkTopology上的DataNode對象還會管理這些動態變化的數據結構,如replicateBlocks/recoverBlocks/invalidateBlocks,這些數據結構正好和BlockManager管理的動態數據結構對應,實現了數據的動態變化由BlockManager傳達到DataNode內存對象最後經過指令下達到物理DataNode實際執行的流動過程,流程在3.2 BlockManager已經介紹。
這裏存在一個問題,爲何DatanodeStorageInfo下全部Block之間會以雙向鏈表組織,而不是其它數據結構?若是結合實際場景就不難發現,對每個DatanodeStorageInfo下Block的操做集中在快速增長/刪除(Block動態增減變化)及順序遍歷(BlockReport期間),因此雙向鏈表是很是合適的數據結構。
Lease 機制是重要的分佈式協議,普遍應用於各類實際的分佈式系統中。HDFS支持Write-Once-Read-Many,對文件寫操做的互斥同步靠Lease實現。Lease其實是時間約束鎖,其主要特色是排他性。客戶端寫文件時須要先申請一個Lease,一旦有客戶端持有了某個文件的Lease,其它客戶端就不可能再申請到該文件的Lease,這就保證了同一時刻對一個文件的寫操做只能發生在一個客戶端。NameNode的LeaseManager是Lease機制的核心,維護了文件與Lease、客戶端與Lease的對應關係,這類信息會隨寫數據的變化實時發生對應改變。
圖9所示爲LeaseManager內存結構,包括如下三個主要核心數據結構:
sortedLeases:Lease集合,按照時間前後有序組織,便於檢查Lease是否超時;
leases:客戶端到Lease的映射關係;
sortedLeasesByPath:文件路徑到Lease的映射關係;
其中每個寫數據的客戶端會對應一個Lease,每一個Lease裏包含至少一個標識文件路徑的Path。Lease自己已經維護了其持有者(客戶端)及該Lease正在操做的文件路徑集合,之因此增長了leases和sortedLeasesByPath爲提升經過Lease持有者或文件路徑快速索引到Lease的性能。
因爲Lease自己的時間約束特性,當Lease發生超時後須要強制回收,內存中與該Lease相關的內容要被及時清除。超時檢查及超時後的處理邏輯由LeaseManager.Monitor統一執行。LeaseManager中維護了兩個與Lease相關的超時時間:軟超時(softLimit)和硬超時(hardLimit),使用場景稍有不一樣。
正常狀況下,客戶端向集羣寫文件前須要向NameNode的LeaseManager申請Lease;寫文件過程當中按期更新Lease時間,以防Lease過時,週期與softLimit相關;寫完數據後申請釋放Lease。整個過程可能發生兩類問題:(1)寫文件過程當中客戶端沒有及時更新Lease時間;(2)寫完文件後沒有成功釋放Lease。兩個問題分別對應爲softLimit和hardLimit。兩種場景都會觸發LeaseManager對Lease超時強制回收。若是客戶端寫文件過程當中沒有及時更新Lease超過softLimit時間後,另外一客戶端嘗試對同一文件進行寫操做時觸發Lease軟超時強制回收;若是客戶端寫文件完成可是沒有成功釋放Lease,則會由LeaseManager的後臺線程LeaseManager.Monitor檢查是否硬超時後統一觸發超時回收。無論是softLimit仍是hardLimit超時觸發的強制Lease回收,處理邏輯都同樣:FSNamesystem.internalReleaseLease,邏輯自己比較複雜,這裏再也不展開,簡單的說先對Lease過時前最後一次寫入的Block進行檢查和修復,以後釋放超時持有的Lease,保證後面其它客戶端的寫入可以正常申請到該文件的Lease。
NameNode內存數據結構很是豐富,這裏對幾個重要的數據結構進行了簡單的描述,除了前面羅列以外,其實還有如SnapShotManager/CacheManager等,因爲其內存佔用有限且有一些特性還還沒有穩定,這裏再也不展開。
隨着集羣中數據規模的不斷積累,NameNode內存佔用隨之成比例增加。不可避免的NameNode內存將逐漸成爲集羣發展的瓶頸,並開始暴漏諸多問題。
一、啓動時間變長。NameNode的啓動過程能夠分紅FsImage數據加載、editlogs回放、Checkpoint、DataNode的BlockReport幾個階段。數據規模較小時,啓動時間能夠控制在~10min之內,當元數據規模達到5億(Namespace中INode數超過2億,Block數接近3億),FsImage文件大小將接近到20GB,加載FsImage數據就須要~14min,Checkpoint須要~6min,再加上其它階段整個重啓過程將持續~50min,極端狀況甚至超過60min,雖然通過多輪優化重啓過程已經可以穩定在~30min,但也很是耗時。若是數據規模繼續增長,啓動過程將同步增長。
二、性能開始降低。HDFS文件系統的全部元數據相關操做基本上均在NameNode端完成,當數據規模的增長致內存佔用變大後,元數據的增刪改查性能會出現降低,且這種降低趨勢會因規模效應及複雜的處理邏輯被放大,相對複雜的RPC請求(如addblock)性能降低更加明顯。
三、NameNode JVM FGC(Full GC)風險較高。主要體如今兩個方面:(1)FGC頻率增長;(2)FGC時間增長且風險不可控。針對NameNode的應用場景,目前看CMS內存回收算法比較主流,正常狀況下,對超過100GB內存進行回收處理時,能夠控制到秒級別的停頓時間,可是若是回收失敗被降級到串行內存回收時,應用的停頓時間將達到數百秒,這對應用自己是致命的。
四、超大JVM Heap Size調試問題。若是線上集羣性能表現變差,不得不經過分析內存才能獲得結論時,會成爲一件異常困難的事情。且不說Dump自己極其費時費力,Dump超大內存時存在極大機率使NameNode不可服務。
針對NameNode內存增加帶來的諸多問題,社區和業界都在持續關注並嘗試不一樣的解決方案。總體上兩個思路:(1)擴展NameNode分散單點負載;(2)引入外部系統支持NameNode內存數據。
從2010年開始社區就投入大量精力持續解決,Federation方案[3]經過對NameNode進行水平擴展分散單點負載的方式解決NameNode的問題,通過幾年的發展該方案逐漸穩定,目前已經被業界普遍使用。除此以外,社區也在嘗試將Namespace存儲值外部的KV存儲系統如LevelDB[4],從而下降NameNode內存負載。
除社區外,業界也在嘗試本身的解決方案。Baidu HDFS2[5]將元數據管理經過主從架構的集羣形式提供服務,本質上是將原生NameNode管理的Namespace和BlockManagement進行物理拆分。其中Namespace負責管理整個文件系統的目錄樹及文件到BlockID集合的映射關係,BlockID到DataNode的映射關係是按照必定的規則分到多個服務節點分佈式管理,這種方案與Lustre有類似之處(Hash-based Partition)。Taobao HDFS2[6]嘗試過採用另外的思路,藉助高速存儲設備,將元數據經過外存設備進行持久化存儲,保持NameNode徹底無狀態,實現NameNode無限擴展的可能。其它相似的諸多方案不一而足。
儘管社區和業界均對NameNode內存瓶頸有成熟的解決方案,可是不必定適用全部的場景,尤爲是中小規模集羣。結合實踐過程和集羣規模發展期可能遇到的NameNode內存相關問題這裏有幾點建議:
合併小文件。正如前面提到,目錄/文件和Block均會佔用NameNode內存空間,大量小文件會下降內存使用效率;另外,小文件的讀寫性能遠遠低於大文件的讀寫,主要緣由對小文件讀寫須要在多個數據源切換,嚴重影響性能。
調整合適的BlockSize。主要針對集羣內文件較大的業務場景,能夠經過調整默認的Block Size大小(參數:dfs.blocksize,默認128M),下降NameNode的內存增加趨勢。
HDFS Federation方案。當集羣和數據均達到必定規模時,僅經過垂直擴展NameNode已不能很好的支持業務發展,能夠考慮HDFS Federation方案實現對NameNode的水平擴展,在解決NameNode的內存問題的同時經過Federation能夠達到良好的隔離性,不會由於單一應用壓垮整集羣。
NameNode在整個HDFS系統架構中佔據舉足輕重的位置,內部數據和處理邏輯相對複雜,本文簡單梳理了NameNode的內存全景及對其中幾個關鍵數據結構,從NameNode內存核心數據視角對NameNode進行了簡單的解讀,並結合實際場景介紹了隨着數據規模的增長,NameNode內存可能遇到的問題及業界各類可借鑑的解決方案。在後續的《HDFS NameNode內存詳解》中,咱們會詳細解讀NameNode的幾個關鍵數據結構,分析各數據結構在JVM Heap使用佔比狀況。
[1] Apache Hadoop, 2016, https://hadoop.apache.org/.
[2] Apache Hadoop Source Code, 2014, https://github.com/apache/hadoop/tree/branch-2.4.1/.
[3] HDFS Federation, 2011, https://issues.apache.org/jira/browse/HDFS-1052.
[4] NemeNode Scalability, 2013, https://issues.apache.org/jira/browse/HDFS-5389.
[5] Baidu HDFS2, 2013, http://static.zhizuzhefu.com/wordpress_cp/uploads/2013/04/a9.pdf.
[6] Taobao HDFS2, 2012, https://github.com/taobao/ADFS.