天天看點

HBase學習之Region

Region是RS上的基本資料服務機關,使用者表格由1個或者多個Region組成,根據Table的Schema定義,在Region内每個ColumnFamily的資料組成一個Store。每個Store内包括一個MemStore和若幹個StoreFile(HFile)組成。如圖(3)所示。本小節将介紹Store内的MemStore、StoreFile(HFile)的内部結構與實作。

HBase學習之Region

1. MemStore原理與實作分析

MemStore是一個記憶體區域,用以緩存Store内最近一批資料的更新操作。對于Region指定的ColumnFamily下的更新操作(Put、Delete),首先根據是否寫WriteAheadLog,決定是否append到HLog檔案,然後更新到Store的MemStore中。顯然,MemStore的容量不會一直增長下去,是以,在每次執行更新操作時,都會判斷RS上所有的MemStore的記憶體容量是否超過門檻值,如果超過門檻值,通過一定的算法,選擇Region上的MemStore上的資料Flush到檔案系統。更詳細的處理流程圖如圖(4)。

HBase學習之Region

MemStore類内的重要的成員變量:

volatile KeyValueSkipListSet kvset;//記憶體中存放更新的KV的資料結構
volatile KeyValueSkipListSet snapshot;//Flush操作時的KV暫存區域
final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();//Flush操作與kvset之間的可重入讀寫鎖
final AtomicLong size;//跟蹤記錄MemStore的占用的Heap記憶體大小
TimeRangeTracker timeRangeTracker;//跟蹤記錄kvset的最小和最大時間戳
TimeRangeTracker snapshotTimeRangeTracker;//跟蹤記錄snapshot的最小和最大時間戳
MemStoreLAB allocator;//實際記憶體配置設定器
*注意 KeyValueSkipListSet是對于jdk提供的ConcurrentSkipListMap的封裝,Map結構是<KeyValue,KeyValue>的形式。Concurrent表示線程安全。SkipList是一種可以代替平衡樹的資料結構,預設是按照Key值升序的。對于ConcurrentSkipListMap的操作的時間複雜度平均在O(logn),設定KeyValue. KVComparator比較KeyValue中Key的順序。*
           

寫入MemStore中的KV,被記錄在kvset中。根據JVM記憶體的垃圾回收政策,在如下條件會觸發Full GC:

  • 記憶體滿或者觸發門檻值。
  • 記憶體碎片過多,造成新的配置設定找不到合适的記憶體空間

RS上服務多個Region,如果不對KV的配置設定空間進行控制的話,由于通路的無序性以及KV長度的不同,每個Region上的KV會無規律地分散在記憶體上。Region執行了MemStore的Flush操作,再經過JVM GC之後就會出現零散的記憶體碎片現象,而進一步資料大量寫入,就會觸發Full-GC。圖(5)顯示這種假設場景的記憶體配置設定過程。

HBase學習之Region

為了解決因為記憶體碎片造成的Full-GC的現象,RegionServer引入了MSLAB(HBASE-3455)。MSLAB全稱是MemStore-Local Allocation Buffers。它通過預先配置設定連續的記憶體塊,把零散的記憶體申請合并,有效改善了過多記憶體碎片導緻的Full GC問題。

MSLAB的工作原理如下:

  • 在MemStore初始化時,建立MemStoreLAB對象allocator。
  • 建立一個2M大小的Chunk數組,偏移量起始設定為0。Chunk的大小可以通過參數hbase.hregion.memstore.mslab.chunksize調整。
  • 當MemStore有KeyValue加入時,maybeCloneWithAllocator(KeyValue)函數調用allocator為其查找KeyValue.getBuffer()大小的空間,若KeyValue的大小低于預設的256K,會嘗試在目前Chunk下查找空間,如果空間不夠,MemStoreLAB重新申請新的Chunk。選中Chunk之後,會修改offset=原偏移量+KeyValue.getBuffer().length。chunk内控制每個KeyValue大小由hbase.hregion.memstore.mslab.max.allocation配置。
  • 空間檢查通過的KeyValue,會拷貝到Chunk的資料塊中。此時,原KeyValue由于不再被MemStore引用,會在接下來的JVM的Minor GC被清理。

注意 設定chunk的預設大小以及對于KeyValue大小控制的原因在于,MSLAB雖然會降低記憶體碎片造成的Full-GC的風險,但是它的使用會降低記憶體的使用率。如果超過一定大小的KeyValue,此時該KeyValue空間被回收之後,碎片現象不明顯。是以,MSLAB隻解決小KV的聚合。

MSLAB解決了因為碎片造成Full GC的問題,然而在MemStore被Flush到檔案系統時,沒有reference的chunk,需要GC來進行回收,是以,在更新操作頻繁發生時,會造成較多的Young GC。

針對該問題,HBASE-8163提出了MemStoreChunkPool的解決方案,方案已經被HBase-0.95版本接收。它的實作思路:

  • 建立chunk池來管理沒有被引用的chunk,不再依靠JVM的GC回收。
  • 當一個chunk沒有引用時,會被放入chunk池。
  • chunk池設定門檻值,如果超過了,則會放棄放入新的chunk到chunk池。
  • 如果當需要新的chunk時,首先從chunk池中擷取。

根據patch的測試顯示,配置MemStoreChunkPool之後,YGC降低了40%,寫性能有5%的提升。如果是0.95以下版本的使用者,可以參考HBASE-8163給出patch。

思考 通過MemStore提供的MSLAB和MemStoreChunkPool給出的解決方案,可以看出在涉及到大規模記憶體的Java應用中,如何有效地管理記憶體空間,降低JVM GC對于系統性能造成的影響,成為了一個研究熱點。整體上來說,一是設定與應用相适應的JVM啟動參數,列印GC相關的資訊,實時監控GC對于服務的影響;二是從應用程式設計層面,盡可能地友好地利用記憶體,來降低GC的影響。

在ChunkPool就是幫助JVM維護了chunk資訊,并把那些已經不再MemStore中的資料的chunk重新投入使用。這樣就可以避免大量的YGC。

2、MemStore參數控制原理與調優

對于任何一個HBase叢集而言,都需要根據應用特點對其系統參數進行配置,以達到更好的使用效果。MemStore作為更新資料的緩存,它的大小及處理方式的調整,會極大地影響到寫資料的性能、以及随之而來的Flush、Compaction等功能。這種影響的原因在于以下兩個方面。

  • RS全局的MemStore的大小與Region規模以及Region寫資料頻度之間的關系。
  • 過于頻繁的Flush操作對于讀資料的影響。

這其中涉及到的可調整的參數如下表。

參數名稱:hbase.regionserver.global.memstore.upperLimit

參數含義:RS内所有MemStore的總和的上限/Heap Size的比例,超過該值,阻塞update,強制執行Flush操作。

預設值:0.4

參數名稱:hbase.regionserver.global.memstore.lowerLimit

參數含義:執行Flush操作釋放記憶體空間,需要達到的比例。

預設值:0.35

參數名稱:hbase.hregion.memstore.flush.size

參數含義:每個MemStore占用空間的最大值,超過該值會執行Flush操作。

預設值:128MB

參數名稱:hbase.hregion.memstore.block.multiplier

參數含義:HRegion的更新被阻塞的MemStore容量的倍數。

預設值:2

參數名稱:hbase.hregion.preclose.flush.size

參數含義:關閉Region之前需要執行Flush操作的MemStore容量門檻值。

預設值:5MB

對于上述參數了解:

(1)RS控制記憶體使用量的穩定。

例如,假設我們的RS的記憶體設定為10GB,按照以上參數的預設值,RS用以MemStore的上限為4GB,超出之後,會阻塞整個RS的所有Reigon的請求,直到全局的MemStore總量回落到正常範圍之内。

以上涉及到cacheFlusher在MemStore總量使用超過上限時,選擇Region進行Flush的算法,由MemStoreFlusher.flushOneForGlobalPressure()算法實作。算法的處理流程如下。

SortedMap<Long,HRegion> regionsBySize =
server.getCopyOfOnlineRegionsSortedBySize();//從RS上擷取線上的Region,以及它們在MemStore上使用量,并按照MemStore使用量作為Key,降序。
Set excludedRegions = new HashSet();//記錄嘗試執行Flush操作失敗的Region
…
HRegion bestFlushableRegion = getBiggestMemstoreRegion(
regionsBySize, excludedRegions, true);//選出storefile個數不超标、目前MemStore使用量最大的Region
HRegion bestAnyRegion = getBiggestMemstoreRegion(
regionsBySize, excludedRegions, false);//選出目前MemStore使用量最大的Region
           

步驟1:RS上線上的Region,按照目前MemStore的使用量進行排序,并存儲在regionsBySize中。

步驟2:選出Region下的Store中的StoreFile的個數未達到hbase.hstore.blockingStoreFiles,并且MemStore使用量最大的Region,存儲到bestFlushableRegion。

步驟3:選出Region下的MemStore使用量最大的Region,存儲到bestAnyRegion對象。

步驟4:如果bestAnyRegion的memstore使用量超出了bestFlushableRegion的兩倍,這從另外一個角度說明,雖然目前bestAnyRegion有超過blockingStoreFiles個數的檔案,但是考慮到RS記憶體的壓力,冒着被執行Compaction的風險,也選擇這個Region作為regionToFlush,因為收益大。否則,直接選擇bestFlushableRegion作為regionToFlush。

步驟5:對regionToFlush執行flush操作。如果操作失敗,regionToFlush放入excludedRegions,避免該Region下次再次被選中,然後傳回步驟2執行,否則程式退出。

(2)設定兩個limit,盡可能減少因為控制記憶體造成資料更新流程的阻塞。

當RS的MemStore使用總量超過

(Heap*hbase.regionserver.global.memstore.lowerLimit)

的大小時,同樣會向cacheFlusher送出一個Flush請求,并以(1)中Region選擇算法,對其進行Flush操作。與(1)不同,這個過程中RS不會阻塞RS的寫請求。

是以,在生産環境中,我們肯定不希望更新操作被block,一般會配置(upperLimit –lowerlimit)的值在[0.5,0.75]之間,如果是應用寫負載較重,可以設定區間内較大的值。

3、 StoreFile—HFile

下篇講解

4 、Compaction對于服務的影響

詳情請參考:

HBase Compaction的前生今世-身世之旅

HBase Compaction的前生今世-改造之路