Compaction流程

觸發(fā)時機(jī)
HBase中可以觸發(fā)compaction的因素有很多,最常見的因素有這么三種:Memstore Flush、后臺線程周期性檢查、手動觸發(fā)。
Memstore Flush: 應(yīng)該說compaction操作的源頭就來自flush操作,memstore flush會產(chǎn)生HFile文件,文件越來越多就需要compact。因此在每次執(zhí)行完Flush操作之后,都會對當(dāng)前Store中的文件數(shù)進(jìn)行判斷,一旦文件數(shù)# > ,就會觸發(fā)compaction。需要說明的是,compaction都是以Store為單位進(jìn)行的,而在Flush觸發(fā)條件下,整個Region的所有Store都會執(zhí)行compact,所以會在短時間內(nèi)執(zhí)行多次compaction。
后臺線程周期性檢查:后臺線程CompactionChecker定期觸發(fā)檢查是否需要執(zhí)行compaction,檢查周期為:hbase.server.thread.wakefrequencyhbase.server.compactchecker.interval.multiplier。和flush不同的是,該線程優(yōu)先檢查文件數(shù)#是否大于,一旦大于就會觸發(fā)compaction。如果不滿足,它會接著檢查是否滿足major compaction條件,簡單來說,如果當(dāng)前store中hfile的最早更新時間早于某個值mcTime,就會觸發(fā)major compaction,HBase預(yù)想通過這種機(jī)制定期刪除過期數(shù)據(jù)。上文mcTime是一個浮動值,浮動區(qū)間默認(rèn)為[7-70.2,7+7*0.2],其中7為hbase.hregion.majorcompaction,0.2為hbase.hregion.majorcompaction.jitter,可見默認(rèn)在7天左右就會執(zhí)行一次major compaction。用戶如果想禁用major compaction,只需要將參數(shù)hbase.hregion.majorcompaction設(shè)為0
手動觸發(fā):一般來講,手動觸發(fā)compaction通常是為了執(zhí)行major compaction,原因有三,其一是因為很多業(yè)務(wù)擔(dān)心自動major compaction影響讀寫性能,因此會選擇低峰期手動觸發(fā);其二也有可能是用戶在執(zhí)行完alter操作之后希望立刻生效,執(zhí)行手動觸發(fā)major compaction;其三是HBase管理員發(fā)現(xiàn)硬盤容量不夠的情況下手動觸發(fā)major compaction刪除大量過期數(shù)據(jù);無論哪種觸發(fā)動機(jī),一旦手動觸發(fā),HBase會不做很多自動化檢查,直接執(zhí)行合并。
選擇合適的HFile
選擇合適的文件進(jìn)行合并是整個compaction的核心,因為合并文件的大小以及其當(dāng)前承載的IO數(shù)直接決定了compaction的效果。最理想的情況是,這些文件承載了大量IO請求但是大小很小,這樣compaction本身不會消耗太多IO,而且合并完成之后對讀的性能會有顯著提升。然而現(xiàn)實情況可能大部分都不會是這樣,在0.96版本和0.98版本,分別提出了兩種選擇策略,在充分考慮整體情況的基礎(chǔ)上選擇最佳方案。無論哪種選擇策略,都會首先對該Store中所有HFile進(jìn)行一一排查,排除不滿足條件的部分文件:
\1. 排除當(dāng)前正在執(zhí)行compact的文件及其比這些文件更新的所有文件(SequenceId更大)
\2. 排除某些過大的單個文件,如果文件大小大于hbase.hzstore.compaction.max.size(默認(rèn)Long最大值),則被排除,否則會產(chǎn)生大量IO消耗
經(jīng)過排除的文件稱為候選文件,HBase接下來會再判斷是否滿足major compaction條件,如果滿足,就會選擇全部文件進(jìn)行合并。判斷條件有下面三條,只要滿足其中一條就會執(zhí)行major compaction:
用戶強(qiáng)制執(zhí)行major compaction
長時間沒有進(jìn)行compact(CompactionChecker的判斷條件2)且候選文件數(shù)小于hbase.hstore.compaction.max(默認(rèn)10)
Store中含有Reference文件,Reference文件是split region產(chǎn)生的臨時文件,只是簡單的引用文件,一般必須在compact過程中刪除
如果不滿足major compaction條件,就必然為minor compaction,HBase主要有兩種minor策略:RatioBasedCompactionPolicy和ExploringCompactionPolicy,下面分別進(jìn)行介紹:
RatioBasedCompactionPolicy
從老到新逐一掃描所有候選文件,滿足其中條件之一便停止掃描:
(1)當(dāng)前文件大小 < 比它更新的所有文件大小總和 * ratio,其中ratio是一個可變的比例,在高峰期時ratio為1.2,非高峰期為5,也就是非高峰期允許compact更大的文件。那什么時候是高峰期,什么時候是非高峰期呢?用戶可以配置參數(shù)hbase.offpeak.start.hour和hbase.offpeak.end.hour來設(shè)置高峰期
(2)當(dāng)前所剩候選文件數(shù) <= hbase.store.compaction.min(默認(rèn)為3)
停止掃描后,待合并文件就選擇出來了,即為當(dāng)前掃描文件+比它更新的所有文件
ExploringCompactionPolicy
該策略思路基本和RatioBasedCompactionPolicy相同,不同的是,Ratio策略在找到一個合適的文件集合之后就停止掃描了,而Exploring策略會記錄下所有合適的文件集合,并在這些文件集合中尋找最優(yōu)解。最優(yōu)解可以理解為:待合并文件數(shù)最多或者待合并文件數(shù)相同的情況下文件大小較小,這樣有利于減少compaction帶來的IO消耗。
需要注意的是,Ratio策略是0.94版本的默認(rèn)策略,而0.96版本之后默認(rèn)策略就換為了Exploring策略,在cloudera博文《what-are-hbase-compactions》中,作者給出了一個兩者的簡單性能對比,基本可以看出后者在節(jié)省IO方面會有10%左右的提升。
截止到此,HBase基本上就選擇出來了待合并的文件集合,后續(xù)通過挑選合適的處理線程,就會對這些文件進(jìn)行真正的合并
選擇合適的線程池
HBase實現(xiàn)中有一個專門的線程CompactSplitThead負(fù)責(zé)接收compact請求以及split請求,而且為了能夠獨立處理這些請求,這個線程內(nèi)部構(gòu)造了多個線程池:largeCompactions、smallCompactions以及splits等,其中splits線程池負(fù)責(zé)處理所有的split請求,largeCompactions和smallCompaction負(fù)責(zé)處理所有的compaction請求,其中前者用來處理大規(guī)模compaction,后者處理小規(guī)模compaction。這里需要明白三點:
上述設(shè)計目的是為了能夠?qū)⒄埱螵毩⑻幚?,提供系統(tǒng)的處理性能。
哪些compaction應(yīng)該分配給largeCompactions處理,哪些應(yīng)該分配給smallCompactions處理?是不是Major Compaction就應(yīng)該交給largeCompactions線程池處理?不對。這里有個分配原則:待compact的文件總大小如果大于值throttlePoint(可以通過參數(shù)hbase.regionserver.thread.compaction.throttle配置,默認(rèn)為2.5G),分配給largeCompactions處理,否則分配給smallCompactions處理。
largeCompactions線程池和smallCompactions線程池默認(rèn)都只有一個線程,用戶可以通過參數(shù)hbase.regionserver.thread.compaction.large和hbase.regionserver.thread.compaction.small進(jìn)行配置
執(zhí)行合并
上文一方面選出了待合并的HFile集合,一方面也選出來了合適的處理線程,萬事俱備,只欠最后真正的合并。合并流程說起來也簡單,主要分為如下幾步:
分別讀出待合并hfile文件的KV,并順序?qū)懙轿挥?/tmp目錄下的臨時文件中
將臨時文件移動到對應(yīng)region的數(shù)據(jù)目錄
將compaction的輸入文件路徑和輸出文件路徑封裝為KV寫入WAL日志,并打上compaction標(biāo)記,最后強(qiáng)制執(zhí)行sync
將對應(yīng)region數(shù)據(jù)目錄下的compaction輸入文件全部刪除
上述四個步驟看起來簡單,但實際是很嚴(yán)謹(jǐn)?shù)?,具有很?qiáng)的容錯性和完美的冪等性:
如果RS在步驟2之前發(fā)生異常,本次compaction會被認(rèn)為失敗,如果繼續(xù)進(jìn)行同樣的compaction,上次異常對接下來的compaction不會有任何影響,也不會對讀寫有任何影響。唯一的影響就是多了一份多余的數(shù)據(jù)。
在步驟2之后、步驟3之前發(fā)生異常,同樣的,僅僅會多一份冗余數(shù)據(jù)。
如果在步驟3之后、步驟4之前發(fā)生異常,RS在重新打開region之后首先會從WAL中看到標(biāo)有compaction的日志,因為此時輸入文件和輸出文件已經(jīng)持久化到HDFS,因此只需要根據(jù)WAL移除掉compaction輸入文件即可
Compaction對于讀寫操作的影響
Compaction與Flush不同之處在于:Flush是針對一個Region整體執(zhí)行操作,而Compaction操作是針對Region上的一個Store而言,因此,從邏輯上看,F(xiàn)lush操作粒度較大。這屬于一個LSM存儲模型最核心的設(shè)計:
1)Flush操作如果只選擇某個Region的Store內(nèi)的MemStore寫入磁盤,而不是統(tǒng)一寫入磁盤,那么HLog上key的一致性在Reigon不同ColumnFamily(Store)下的MemStore內(nèi)就會有不一致的key區(qū)間。
如下圖所示,我們假定該RegionServer上僅有一個Region,由于不同的Row是在列簇上有所區(qū)別,就會出現(xiàn)有些不同Store內(nèi)占用的內(nèi)存不一致的情況,這里會根據(jù)整體內(nèi)存使用的情況,或者RS使用內(nèi)存的情況來決定是否執(zhí)行Flush操作。如果僅僅刷入使用內(nèi)存較大的memstore,那么在使用的過程中,一是Scan操作在執(zhí)行時就不夠統(tǒng)一,二是在HLog Replayer還原Region內(nèi)Memstore故障前的狀態(tài),只需根據(jù)Hlog的Flush_marker的標(biāo)記位來執(zhí)行Replay即可。
2)Compaction執(zhí)行結(jié)束之后會生成臨時文件,臨時文件所在的hdfs位置如下:
/hbase-weibo/bi_weibo_cluster/ffd87a50c3df3080183d4910d183d0ee/.tmp
ffd87a50c3df3080183d4910d183d0ee 是bi_weibo_cluster表格的Region名。臨時文件的意義在于,在Compaction執(zhí)行期間,對于原數(shù)據(jù)訪問沒有影響。Compaction執(zhí)行合并操作生成的文件生效過程,需要對Store的寫操作加鎖,阻塞Store內(nèi)的更新操作,直到更新Store的storeFiles完成為止。(注意,這個操作過程執(zhí)行會影響到更新服務(wù),但是影響不會太大)
3)對于讀服務(wù)的影響,類似于Flush操作,也是通過ChangedReaderObserver為StoreScanner注冊監(jiān)聽類來實現(xiàn)的