HBase BlockCache系列 - 探求BlockCache实现机制
轉載自:http://hbasefly.com/2016/04/26/hbase-blockcache-2/
HBase BlockCache系列第一篇文章《走進BlockCache》從全局視角對HBase中緩存、Memstore等作了簡要概述,并重點介紹了幾種BlockCache方案及其演進過程,對此還不了解的可以點這里。本文在上文的基礎上深入BlockCache內部,對各種BlockCache方案具體工作原理進行詳細分析。Note:因為SlabCache方案在0.98版本已經不被建議使用,因此本文不針對該方案進行講解;至于LRU方案和Bucket方案,因為后者更加復雜,本文也會花更多篇幅詳細介紹該方案的實現細節。
?
LRUBlockCache
LRUBlockCache是HBase目前默認的BlockCache機制,實現機制比較簡單。它使用一個ConcurrentHashMap管理BlockKey到Block的映射關系,緩存Block只需要將BlockKey和對應的Block放入該HashMap中,查詢緩存就根據BlockKey從HashMap中獲取即可。同時該方案采用嚴格的LRU淘汰算法,當Block Cache總量達到一定閾值之后就會啟動淘汰機制,最近最少使用的Block會被置換出來。在具體的實現細節方面,需要關注三點:
?
1. 緩存分層策略
HBase在LRU緩存基礎上,采用了緩存分層設計,將整個BlockCache分為三個部分:single-access、mutil-access和inMemory。需要特別注意的是,HBase系統元數據存放在InMemory區,因此設置數據屬性InMemory = true需要非常謹慎,確保此列族數據量很小且訪問頻繁,否則有可能會將hbase.meta元數據擠出內存,嚴重影響所有業務性能。
?
2. LRU淘汰算法實現
系統在每次cache block時將BlockKey和Block放入HashMap后都會檢查BlockCache總量是否達到閾值,如果達到閾值,就會喚醒淘汰線程對Map中的Block進行淘汰。系統設置三個MinMaxPriorityQueue隊列,分別對應上述三個分層,每個隊列中的元素按照最近最少被使用排列,系統會優先poll出最近最少使用的元素,將其對應的內存釋放。可見,三個分層中的Block會分別執行LRU淘汰算法進行淘汰。
?
3. LRU方案優缺點
LRU方案使用JVM提供的HashMap管理緩存,簡單有效。但隨著數據從single-access區晉升到mutil-access區,基本就伴隨著對應的內存對象從young區到old區 ,晉升到old區的Block被淘汰后會變為內存垃圾,最終由CMS回收掉(Conccurent Mark Sweep,一種標記清除算法),然而這種算法會帶來大量的內存碎片,碎片空間一直累計就會產生臭名昭著的Full GC。尤其在大內存條件下,一次Full GC很可能會持續較長時間,甚至達到分鐘級別。大家知道Full GC是會將整個進程暫停的(稱為stop-the-wold暫停),因此長時間Full GC必然會極大影響業務的正常讀寫請求。也正因為這樣的弊端,SlabCache方案和BucketCache方案才會橫空出世。
?
?
BucketCache
相比LRUBlockCache,BucketCache實現相對比較復雜。它沒有使用JVM 內存管理算法來管理緩存,而是自己對內存進行管理,因此不會因為出現大量碎片導致Full GC的情況發生。本節主要介紹BucketCache的具體實現方式(包括BucketCache的內存組織形式、緩存寫入讀取流程等)以及如何配置使用BucketCache。
?
?
內存組織形式
下圖是BucketCache的內存組織形式圖,其中上面部分是邏輯組織結構,下面部分是對應的物理組織結構。HBase啟動之后會在內存中申請大量的bucket,如下圖中黃色矩形所示,每個bucket的大小默認都為2MB。每個bucket會有一個baseoffset變量和一個size標簽,其中baseoffset變量表示這個bucket在實際物理空間中的起始地址,因此block的物理地址就可以通過baseoffset和該block在bucket的偏移量唯一確定;而size標簽表示這個bucket可以存放的block塊的大小,比如圖中左側bucket的size標簽為65KB,表示可以存放64KB的block,右側bucket的size標簽為129KB,表示可以存放128KB的block。
?
HBase中使用BucketAllocator類實現對Bucket的組織管理:
1. HBase會根據每個bucket的size標簽對bucket進行分類,相同size標簽的bucket由同一個BucketSizeInfo管理,如上圖,左側存放64KB block的bucket由65KB BucketSizeInfo管理,右側存放128KB block的bucket由129KB BucketSizeInfo管理。
2. HBase在啟動的時候就決定了size標簽的分類,默認標簽有(4+1)K、(8+1)K、(16+1)K?…?(48+1)K、(56+1)K、(64+1)K、(96+1)K … (512+1)K。而且系統會首先從小到大遍歷一次所有size標簽,為每種size標簽分配一個bucket,最后所有剩余的bucket都分配最大的size標簽,默認分配?(512+1)K,如下圖所示:
?
3.?Bucket的size標簽可以動態調整,比如64K的block數目比較多,65K的bucket被用完了以后,其他size標簽的完全空閑的bucket可以轉換成為65K的bucket,但是至少保留一個該size的bucket。
?
?
Block緩存寫入、讀取流程
下圖是block寫入緩存以及從緩存中讀取block的流程示意圖,圖中主要包括5個模塊,其中RAMCache是一個存儲blockkey和block對應關系的HashMap;WriteThead是整個block寫入的中心樞紐,主要負責異步的寫入block到內存空間;BucketAllocator在上一節詳細介紹過,主要實現對bucket的組織管理,為block分配內存空間;IOEngine是具體的內存管理模塊,主要實現將block數據寫入對應地址的內存空間;BackingMap也是一個HashMap,用來存儲blockKey與對應物理內存偏移量的映射關系,用來根據blockkey定位具體的block;其中紫線表示cache block流程,綠線表示get block流程。
?
?
Block緩存寫入流程
1. 將block寫入RAMCache。實際實現中,HBase設置了多個RAMCache,系統首先會根據blockkey進行hash,根據hash結果將block分配到對應的RAMCache中;
2. WriteThead從RAMCache中取出所有的block。和RAMCache相同,HBase會同時啟動多個WriteThead并發的執行異步寫入,每個WriteThead對應一個RAMCache;
3. 每個WriteThead會將遍歷RAMCache中所有block數據,分別調用bucketAllocator為這些block分配內存空間;
4. BucketAllocator會選擇與block大小對應的bucket進行存放(具體細節可以參考上節‘內存組織形式’所述),并且返回對應的物理地址偏移量offset;
5. WriteThead將block以及分配好的物理地址偏移量傳給IOEngine模塊,執行具體的內存寫入操作;
6. 寫入成功后,將類似<blockkey,offset>這樣的映射關系寫入BackingMap中,方便后續查找時根據blockkey可以直接定位;
?
?
Block緩存讀取流程
1. 首先從RAMCache中查找。對于還沒有來得及寫入到bucket的緩存block,一定存儲在RAMCache中;
2. 如果在RAMCache中沒有找到,再在BackingMap中根據blockKey找到對應物理偏移地址offset;
3. 根據物理偏移地址offset可以直接從內存中查找對應的block數據;
?
?
?
BucketCache工作模式
BucketCache默認有三種工作模式:heap、offheap和file;這三種工作模式在內存邏輯組織形式以及緩存流程上都是相同的,參見上節講解。不同的是三者對應的最終存儲介質有所不同,即上述所講的IOEngine有所不同。
?
其中heap模式和offheap模式都使用內存作為最終存儲介質,內存分配查詢也都使用Java NIO ByteBuffer技術,不同的是,heap模式分配內存會調用byteBuffer.allocate方法,從JVM提供的heap區分配,而后者會調用byteBuffer.allocateDirect方法,直接從操作系統分配。這兩種內存分配模式會對HBase實際工作性能產生一定的影響。影響最大的無疑是GC ,相比heap模式,offheap模式因為內存屬于操作系統,所以基本不會產生CMS GC,也就在任何情況下都不會因為內存碎片導致觸發Full GC。除此之外,在內存分配以及讀取方面,兩者性能也有不同,比如,內存分配時heap模式需要首先從操作系統分配內存再拷貝到JVM heap,相比offheap直接從操作系統分配內存更耗時;但是反過來,讀取緩存時heap模式可以從JVM heap中直接讀取,而offheap模式則需要首先從操作系統拷貝到JVM heap再讀取,顯得后者更費時。
?
file模式和前面兩者不同,它使用Fussion-IO或者SSD等作為存儲介質,相比昂貴的內存,這樣可以提供更大的存儲容量,因此可以極大地提升緩存命中率。
?
BucketCache配置使用
BucketCache方案的配置說明一直被HBaser所詬病,官方一直沒有相關文檔對此進行介紹。本人也是一直被其所困,后來通過查看源碼才基本了解清楚,在此分享出來,以便大家學習。需要注意的是,BucketCache三種工作模式的配置會有所不同,下面也是分開介紹,并且沒有列出很多不重要的參數:
heap模式
<hbase.bucketcache.ioengine>heap</hbase.bucketcache.ioengine> //bucketcache占用整個jvm內存大小的比例 <hbase.bucketcache.size>0.4</hbase.bucketcache.size> //bucketcache在combinedcache中的占比 <hbase.bucketcache.combinedcache.percentage>0.9</hbase.bucketcache.combinedcache.percentage>offheap模式
<hbase.bucketcache.ioengine>offheap</hbase.bucketcache.ioengine> <hbase.bucketcache.size>0.4</hbase.bucketcache.size> <hbase.bucketcache.combinedcache.percentage>0.9</hbase.bucketcache.combinedcache.percentage>file模式
<hbase.bucketcache.ioengine>file:/cache_path</hbase.bucketcache.ioengine> //bucketcache緩存空間大小,單位為MB <hbase.bucketcache.size>10 * 1024</hbase.bucketcache.size> //高速緩存路徑 <hbase.bucketcache.persistent.path>file:/cache_path</hbase.bucketcache.persistent.path>?
?
?
總結
HBase中緩存的設置對隨機讀寫性能至關重要,本文通過對LRUBlockCache和BucketCache兩種方案的實現進行介紹,希望能夠讓各位看官更加深入地了解BlockCache的工作原理。BlockCache系列經過兩篇內容的介紹,基本已經解析完畢,那在實際線上應用中到底應該選擇哪種方案呢?下一篇文章讓我們一起看看HBase社區發布的BlockCache報告!
總結
以上是生活随笔為你收集整理的HBase BlockCache系列 - 探求BlockCache实现机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 汉字转拼音 Scala 汉字
- 下一篇: cuda的shared momery