標籤:

HDFS NameNode內存詳解

前言

《HDFS NameNode內存全景》中,我們從NameNode內部數據結構的視角,對它的內存全景及幾個關鍵數據結構進行了簡單解讀,並結合實際場景介紹了NameNode可能遇到的問題,還有業界進行橫向擴展方面的多種可借鑒解決方案。

事實上,對NameNode實施橫向擴展前,會面臨常駐內存隨數據規模持續增長的情況,為此需要經歷不斷調整NameNode內存的堆空間大小的過程,期間會遇到幾個問題:

  • 當前內存空間預期能夠支撐多長時間。
  • 何時調整堆空間以應對數據規模增長。
  • 增加多大堆空間。

另一方面NameNode堆空間又不能無止境增加,到達閾值後(與機型、JVM版本、GC策略等相關)同樣會存在潛在問題:

  • 重啟時間變長。
  • 潛在的FGC風險。

由此可見,對NameNode內存使用情況的細粒度掌控,可以為優化內存使用或調整內存大小提供更好的決策支持。

本文在前篇《HDFS NameNode內存全景》文章的基礎上,針對前面的幾個問題,進一步對NameNode核心數據結構的內存使用情況進行詳細定量分析,並給出可供參考的內存預估模型。根據分析結果可有針對的優化集群存儲資源使用模式,同時利用內存預估模型,可以提前對內存資源進行合理規劃,為HDFS的發展提供數據參考依據。

內存分析

NetworkTopology

NameNode通過NetworkTopology維護整個集群的樹狀拓撲結構,當集群啟動過程中,通過機架感知(通常都是外部腳本計算)逐漸建立起整個集群的機架拓撲結構,一般在NameNode的生命周期內不會發生大變化。拓撲結構的葉子節點DatanodeDescriptor是標識DataNode的關鍵結構,該類繼承關係如圖1所示。

圖1 DatanodeDescriptor繼承關係

在64位JVM中,DatanodeDescriptor內存使用情況如圖2所示(除特殊說明外,後續對其它數據結構的內存使用情況分析均基於64位JVM)。

圖2 DatanodeDescriptor內存使用詳解

由於DataNode節點一般會掛載多塊不同類型存儲單元,如HDD、SSD等,圖2中storageMap描述的正是存儲介質DatanodeStorageInfo集合,其詳細數據結構如圖3所示。

圖3 DatanodeStorageInfo內存使用詳解

除此之外,DatanodeDescriptor還包括一部分動態內存對象,如replicateBlocks、recoverBlocks和invalidateBlocks等與數據塊動態調整相關的數據結構,pendingCached、cached和pendingUncached等與集中式緩存相關的數據結構。由於這些數據均屬動態的形式臨時存在,隨時會發生變化,所以這裡沒有做進一步詳細統計(結果存在少許誤差)。

根據前面的分析,假設集群中包括2000個DataNode節點,NameNode維護這部分信息需要佔用的內存總量:

(64 + 114 + 56 + 109 ? 16)? 2000 = ~4MB

在樹狀機架拓撲結構中,除了葉子節點DatanodeDescriptor外,還包括內部節點InnerNode描述集群拓撲結構中機架信息。

圖4 NetworkTopology拓撲結構內部節點內存使用詳解

對於這部分描述機架信息等節點信息,假設集群包括80個機架和2000個DataNode節點,NameNode維護拓撲結構中內部節點信息需要佔用的內存總量:

(44 + 48) ? 80 + 8 ? 2000 = ~25KB

從上面的分析可以看到,為維護集群的拓撲結構NetworkTopology,當集群規模為2000時,需要的內存空間不超過5MB,按照接近線性增長趨勢,即使集群規模接近10000,這部分內存空間~25MB,相比整個NameNode JVM的內存開銷微乎其微。

NameSpace

與傳統單機文件系統相似,HDFS對文件系統的目錄結構也是按照樹狀結構維護,NameSpace保存的正是整個目錄樹及目錄樹上每個目錄/文件節點的屬性,包括:名稱(name),編號(id),所屬用戶(user),所屬組(group),許可權(permission),修改時間(mtime),訪問時間(atime),子目錄/文件(children)等信息。

下圖5為Namespace中INode的類圖結構,從類圖可以看出,文件INodeFile和目錄INodeDirectory的繼承關係。其中目錄在內存中由INodeDirectory對象來表示,並用List children成員列表來描述該目錄下的子目錄或文件;文件在內存中則由INodeFile來表示,並用BlockInfo[] blocks數組表示該文件由哪些Blocks組成。其它屬性由繼承關係的各個相應子類成員變數標識。

圖5 文件和目錄繼承關係

目錄和文件結構在繼承關係中各屬性的內存佔用情況如圖6所示。

圖6 目錄和文件內存使用詳解

除圖中提到的屬性信息外,一些附加如ACL等非通用屬性,沒有在統計範圍內。在默認場景下,INodeFile和INodeDirectory.withQuotaFeature是相對通用和廣泛使用到的兩個結構。

根據前面的分析,假設HDFS目錄和文件數分別為1億,Block總量在1億情況下,整個Namespace在JVM中內存使用情況:

Total(Directory) = (24 + 96 + 44 + 48) ? 100M + 8 ? num(total children)

Total(Files) = (24 + 96 + 48) ? 100M + 8 ? num(total blocks)

Total = (24 + 96 + 44 + 48) ? 100M + 8 ? num(total children) + (24 + 96 + 48) ? 100M + 8 ? num(total blocks) = ~38GB

關於預估方法的幾點說明:

  1. 對目錄樹結構中所有的Directory均按照默認INodeDirectory.withQuotaFeature結構進行估算,如果集群開啟ACL/Snapshotd等特性,需增加這部分內存開銷。
  2. 對目錄樹結構中所有的File按照INodeFile進行估算。
  3. 從整個目錄樹的父子關係上看,num(total children)就是目錄節點數和文件節點數之和。
  4. 部分數據結構中包括了字元串,按照均值長度為8進行預估,實際情況可能會稍大。

Namespace在JVM堆內存空間中常駐,在NameNode的整個生命周期一直在內存存在,同時為保證數據的可靠性,NameNode會定期對其進行Checkpoint,將Namespace物化到外部存儲設備。隨著數據規模的增加,文件數/目錄樹也會隨之增加,整個Namespace所佔用的JVM內存空間也會基本保持線性同步增加。

BlocksMap

HDFS將文件按照一定的大小切成多個Block,為了保證數據可靠性,每個Block對應多個副本,存儲在不同DataNode上。NameNode除需要維護Block本身的信息外,還需要維護從Block到DataNode列表的對應關係,用於描述每一個Block副本實際存儲的物理位置,BlockManager中BlocksMap結構即用於Block到DataNode列表的映射關係。BlocksMap內部數據結構如圖7所示。

圖7 BlockInfo繼承關係

BlocksMap經過多次優化形成當前結構,最初版本直接使用HashMap解決從Block到BlockInfo的映射。由於在內存使用、碰撞衝突解決和性能等方面存在問題,之後使用重新實現的LightWeightGSet代替HashMap,該數據結構本質上也是利用鏈表解決碰撞衝突的HashTable,但是在易用性、內存佔用和性能等方面表現更好。關於引入LightWeightGSet細節可參考HDFS-1114。

與HashMap相比,為了儘可能避免碰撞衝突,BlocksMap在初始化時直接分配整個JVM堆空間的2%作為LightWeightGSet的索引空間,當然2%不是絕對值,如果2%內存空間可承載的索引項超出了Integer.MAX_VALUE/8(註:Object.hashCode()結果是int,對於64位JVM的對象引用佔用8Bytes)會將其自動調整到閾值上限。限定JVM堆空間的2%基本上來自經驗值,假定對於64位JVM環境,如果提供64GB內存大小,索引項可超過1億,如果Hash函數適當,基本可以避免碰撞衝突。

BlocksMap的核心功能是通過BlockID快速定位到具體的BlockInfo,關於BlockInfo詳細的數據結構如圖8所示。BlockInfo繼承自Block,除了Block對象中BlockID,numbytes和timestamp信息外,最重要的是該Block物理存儲所在的對應DataNode列表信息triplets。

圖8 BlocksMap內存使用詳解

其中LightWeightGSet對應的內存空間全局唯一。儘管經過LightWeightGSet優化內存佔用,但是BlocksMap仍然佔用了大量JVM內存空間,假設集群中共1億Block,NameNode可用內存空間固定大小128GB,則BlocksMap佔用內存情況:

16 + 24 + 2% ? 128GB +( 40 + 128 )? 100M = ~20GB

BlocksMap數據在NameNode整個生命周期內常駐內存,隨著數據規模的增加,對應Block數會隨之增多,BlocksMap所佔用的JVM堆內存空間也會基本保持線性同步增加。

小結

NameNode內存數據結構非常豐富,除了前面詳細分析的核心數據結構外,其實還包括如LeaseManager/SnapShotManager/CacheManager等管理的數據,由於內存使用非常有限,或特性未穩定沒有開啟,或沒有通用性,這裡都不再展開。

根據前述對NameNode內存的預估,對比Hadoop集群歷史實際數據:文件目錄總量~140M,數據塊總量~160M,NameNode JVM配置72GB,預估內存使用情況:

Namespace:(24 + 96 + 44 + 48) ? 70M + 8 ? 140M + (24 + 96 + 48) ? 70M + 8 ? 160M = ~27GB

BlocksMap:16 + 24 + 2% ? 72GB +( 40 + 128 )? 160M = ~26GB

說明:這裡按照目錄文件數佔比1:1進行了簡化,基本與實際情況吻合,且簡化對內存預估結果影響非常小。

二者組合結果~53GB,結果與監控數據顯示常駐內存~52GB基本相同,符合實際情況。

從前面討論可以看出,整個NameNode堆內存中,占空間最大的兩個結構為Namespace和BlocksMap,當數據規模增加後,巨大的內存佔用勢必會給JVM內存管理帶來挑戰,甚至可能制約NameNode服務能力邊界。

針對Namespace和BlocksMap的空間佔用規模,有兩個優化方向:

  • 合併小文件。使用Hive做數據生產時,為避免嚴重的數據傾斜、人為調小分區粒度等一些特殊原因,可能會在HDFS上寫入大量小文件,會給NameNode帶來潛在的影響。及時合併小文件,保持穩定的目錄文件增長趨勢,可有效避免NameNode內存抖動。
  • 適當調整BlockSize。如前述,更少的Block數也可降低內存使用,不過BlockSize調整會間接影響到計算任務,需要進行適當的權衡。

對比其他Java服務,NameNode場景相對特殊,需要對JVM部分默認參數進行適當調整。比如Young/Old空間比例,為避免CMS GC降級到FGC影響服務可用性,適當調整觸發CMS GC開始的閾值等等。關於JVM相關參數調整策略的細節建議參考官方使用文檔。

這裡,筆者根據實踐提供幾點NameNode內存相關的經驗供參考:

  • 根據元數據增長趨勢,參考本文前述的內存空間佔用預估方法,能夠大體得到NameNode常駐內存大小,一般按照常駐內存占內存總量~60%調整JVM內存大小可基本滿足需求。
  • 為避免GC出現降級的問題,可將CMSInitiatingOccupancyFraction調整到~70。
  • NameNode重啟過程中,尤其是DataNode進行BlockReport過程中,會創建大量臨時對象,為避免其晉陞到Old區導致頻繁GC甚至誘發FGC,可適當調大Young區(-XX:NewRatio)到10~15。

據了解,針對NameNode的使用場景,使用CMS內存回收策略,將HotSpot JVM內存空間調整到180GB,可提供穩定服務。繼續上調有可能對JVM內存管理能力帶來挑戰,尤其是內存回收方面,一旦發生FGC對應用是致命的。這裡提到180GB大小並不是絕對值,能否在此基礎上繼續調大且能夠穩定服務不在本文的討論範圍。結合前述的預估方法,當可用JVM內存達180GB時,可管理元數據總量達~700M,基本能夠滿足中小規模以下集群需求。

總結

本文在《HDFS NameNode內存全景》基礎上,對NameNode內存使用佔比較高的幾個核心數據結構進行了詳細的介紹。在此基礎上,提供了可供參考的NameNode內存數據空間佔用預估模型:

Total = 198 ? num(Directory + Files) + 176 ? num(blocks) + 2% ? size(JVM Memory Size)

通過對NameNode內存使用情況的定量分析,可為HDFS優化和發展規劃提供可借鑒的數據參考依據。

參考文獻

[1] Apache Hadoop. Welcome to Apache? Hadoop?!. 2016.

[2] Apache Issues. issues.apache.org. 2016.

[3] Apache Hadoop Source Code. apache/hadoop. 2014.

[4] HDFS NameNode內存全景. HDFS NameNode內存全景. 2016.

[5] Java HotSpot VM Options. Java HotSpot VM Options.

不想錯過技術博客更新?想給文章評論、和作者互動?第一時間獲取技術沙龍信息?

請關注我們的官方微信公眾號「美團點評技術團隊」。

推薦閱讀:

Azkaban任務流編寫
Apache kylin進階——Slow Query SQL改造篇
大數據那些事(30):Presto之坑和蘿蔔傻子和騙子的故事
Azkaban二次開發3-Hadoop任務提交方式改造
SparkSQL中的Sort實現(二)

TAG:Hadoop | HDFS |