簡(jiǎn)介
Region內(nèi)每個(gè)ColumnFamily的數(shù)據(jù)組成一個(gè)Store和泌。每個(gè)Store內(nèi)包括一個(gè)MemStore和若干個(gè)StoreFile(HFile)組成勋眯。
Memstore類重要成員變量
volatile KeyValueSkipListSet kvset;*//內(nèi)存中存放更新的KV的數(shù)據(jù)結(jié)構(gòu)*
volatile KeyValueSkipListSet snapshot;*//Flush操作時(shí)的KV暫存區(qū)域*
final ReentrantReadWriteLock lock = **new** ReentrantReadWriteLock();*//Flush操作與kvset之間的可重入讀寫鎖*
final AtomicLong size;*//跟蹤記錄MemStore的占用的Heap內(nèi)存大小*
TimeRangeTracker timeRangeTracker;*//跟蹤記錄kvset的最小和最大時(shí)間戳*
TimeRangeTracker snapshotTimeRangeTracker;*//跟蹤記錄snapshot的最小和最大時(shí)間戳*
MemStoreLAB allocator;*//實(shí)際內(nèi)存分配器*</pre>
Memstore 工作流程
Memstore Flush
HBase為了方便按照RowKey進(jìn)行檢索,要求HFile中數(shù)據(jù)都按照RowKey進(jìn)行排序凿渊,Memstore數(shù)據(jù)在flush為HFile之前會(huì)進(jìn)行一次排序
為了減少flush過程對(duì)讀寫的影響笼呆,HBase采用了類似于兩階段提交的方式,將整個(gè)flush過程分為三個(gè)階段:
prepare階段:遍歷當(dāng)前Region中的所有Memstore昧碉,將Memstore中當(dāng)前數(shù)據(jù)集kvset做一個(gè)快照snapshot英染,然后再新建一個(gè)新的kvset。后期的所有寫入操作都會(huì)寫入新的kvset中被饿,而整個(gè)flush階段讀操作會(huì)首先分別遍歷kvset和snapshot四康,如果查找不到再會(huì)到HFile中查找。prepare階段需要加一把updateLock對(duì)寫請(qǐng)求阻塞锹漱,結(jié)束之后會(huì)釋放該鎖箭养。因?yàn)榇穗A段沒有任何費(fèi)時(shí)操作,因此持鎖時(shí)間很短哥牍。
flush階段:遍歷所有Memstore毕泌,將prepare階段生成的snapshot持久化為臨時(shí)文件,臨時(shí)文件會(huì)統(tǒng)一放到目錄.tmp下嗅辣。這個(gè)過程因?yàn)樯婕暗酱疟PIO操作撼泛,因此相對(duì)比較耗時(shí)。
commit階段:遍歷所有的Memstore澡谭,將flush階段生成的臨時(shí)文件移到指定的ColumnFamily目錄下愿题,針對(duì)HFile生成對(duì)應(yīng)的storefile和Reader,把storefile添加到HStore的storefiles列表中,最后再清空prepare階段生成的snapshot潘酗。
頻繁的Memstore Flushes
要避免“寫阻塞”杆兵,貌似讓Flush操作盡量的早于達(dá)到觸發(fā)“寫操作”的閾值為宜。但是仔夺,這將導(dǎo)致頻繁的Flush操作琐脏,而由此帶來的后果便是讀性能下降以及額外的負(fù)載。
每次的Memstore Flush都會(huì)為每個(gè)CF創(chuàng)建一個(gè)HFile缸兔。頻繁的Flush就會(huì)創(chuàng)建大量的HFile日裙。這樣HBase在檢索的時(shí)候,就不得不讀取大量的HFile惰蜜,讀性能會(huì)受很大影響昂拂。
為預(yù)防打開過多HFile及避免讀性能惡化,HBase有專門的HFile合并處理(HFile Compaction Process)抛猖。HBase會(huì)周期性的合并數(shù)個(gè)小HFile為一個(gè)大的HFile格侯。明顯的,有Memstore Flush產(chǎn)生的HFile越多樟结,集群系統(tǒng)就要做更多的合并操作(額外負(fù)載)养交。更糟糕的是:Compaction處理是跟集群上的其他請(qǐng)求并行進(jìn)行的。當(dāng)HBase不能夠跟上Compaction的時(shí)候(同樣有閾值設(shè)置項(xiàng))瓢宦,會(huì)在RS上出現(xiàn)“寫阻塞”碎连。像上面說到的,這是最最不希望的驮履。
提示:嚴(yán)重關(guān)切RS上Compaction Queue 的size鱼辙。要在其引起問題前,阻止其持續(xù)增大玫镐。
想了解更多HFile 創(chuàng)建和合并倒戏,可參看 Visualizing HBase Flushes And Compactions。
理想情況下恐似,在不超過hbase.regionserver.global.memstore.upperLimit的情況下杜跷,Memstore應(yīng)該盡可能多的使用內(nèi)存(配置給Memstore部分的,而不是真?zhèn)€Heap的)矫夷。下圖展示了一張“較好”的情況:
KeyValueSkipListSet
hbase使用的是jdk提供的ConcurrentSkipListMap葛闷,并對(duì)其進(jìn)行了的封裝,Map結(jié)構(gòu)是<KeyValue,KeyValue>的形式双藕。Concurrent表示線程安全淑趾。
SkipList是一種高效的數(shù)據(jù)結(jié)構(gòu),之前專門寫過文章忧陪,這里就不表了
Memstore 引起的內(nèi)存碎片問題
寫入MemStore中的KV扣泊,被記錄在kvset中近范。根據(jù)JVM內(nèi)存的垃圾回收策略,在如下條件會(huì)觸發(fā)Full GC延蟹。 1评矩、內(nèi)存滿或者觸發(fā)閾值。 2等孵、內(nèi)存碎片過多稚照,造成新的分配找不到合適的內(nèi)存空間蹂空。 RS上服務(wù)多個(gè)Region俯萌,如果不對(duì)KV的分配空間進(jìn)行控制的話,由于訪問的無(wú)序性以及KV長(zhǎng)度的不同上枕,每個(gè)Region上的KV會(huì)無(wú)規(guī)律地分散在內(nèi)存上咐熙。Region執(zhí)行了MemStore的Flush操作,再經(jīng)過JVM GC之后就會(huì)出現(xiàn)零散的內(nèi)存碎片現(xiàn)象辨萍,而進(jìn)一步數(shù)據(jù)大量寫入棋恼,就會(huì)觸發(fā)Full-GC。
為了解決因?yàn)閮?nèi)存碎片造成的Full-GC的現(xiàn)象锈玉,RegionServer引入了MSLAB(HBASE-3455)爪飘。MSLAB全稱是MemStore-Local Allocation Buffers。它通過預(yù)先分配連續(xù)的內(nèi)存塊拉背,把零散的內(nèi)存申請(qǐng)合并师崎,有效改善了過多內(nèi)存碎片導(dǎo)致的Full GC問題。 MSLAB的工作原理如下: 1椅棺、在MemStore初始化時(shí)犁罩,創(chuàng)建MemStoreLAB對(duì)象allocator。 2两疚、創(chuàng)建一個(gè)2M大小的Chunk數(shù)組床估,偏移量起始設(shè)置為0。Chunk的大小可以通過參數(shù)hbase.hregion.memstore.mslab.chunksize調(diào)整诱渤。 3丐巫、 當(dāng)MemStore有KeyValue加入時(shí),maybeCloneWithAllocator(KeyValue)函數(shù)調(diào)用allocator為其查找KeyValue.getBuffer()大小的空間勺美,若KeyValue的大小低于默認(rèn)的256K递胧,會(huì)嘗試在當(dāng)前Chunk下查找空間,如果空間不夠励烦,MemStoreLAB重新申請(qǐng)新的Chunk谓着。選中Chunk之后,會(huì)修改offset=原偏移量+KeyValue.getBuffer().length坛掠。chunk內(nèi)控制每個(gè)KeyValue大小由hbase.hregion.memstore.mslab.max.allocation配置赊锚。 4治筒、 空間檢查通過的KeyValue,會(huì)拷貝到Chunk的數(shù)據(jù)塊中舷蒲。此時(shí)耸袜,原KeyValue由于不再被MemStore引用,會(huì)在接下來的JVM的Minor GC被清理牲平。
MSLAB解決了因?yàn)樗槠斐蒄ull GC的問題堤框,然而在MemStore被Flush到文件系統(tǒng)時(shí),沒有reference的chunk纵柿,需要GC來進(jìn)行回收蜈抓,因此,在更新操作頻繁發(fā)生時(shí)昂儒,會(huì)造成較多的Young GC沟使。 針對(duì)該問題,HBASE-8163提出了MemStoreChunkPool的解決方案渊跋,方案已經(jīng)被HBase-0.95版本接收腊嗡。它的實(shí)現(xiàn)思路: 1、 創(chuàng)建chunk池來管理沒有被引用的chunk拾酝,不再依靠JVM的GC回收燕少。 2、 當(dāng)一個(gè)chunk沒有引用時(shí)蒿囤,會(huì)被放入chunk池客们。 3、chunk池設(shè)置閾值蟋软,如果超過了镶摘,則會(huì)放棄放入新的chunk到chunk池。 4岳守、 如果當(dāng)需要新的chunk時(shí)凄敢,首先從chunk池中獲取。 根據(jù)patch的測(cè)試顯示湿痢,配置MemStoreChunkPool之后涝缝,YGC降低了40%,寫性能有5%的提升譬重。如果是0.95以下版本的用戶拒逮,可以參考HBASE-8163給出patch。