hbase Compaction

Compaction會從一個region的一個store中選擇一些hfile文件進行合併。合併說來原理很簡單,先從這些待合併的數據文件中讀出KeyValues,再按照由小到大排列後寫入一個新的文件中。以後,這個新生成的文件就會取代以前待合併的全部文件對外提供服務。HBase根據合併規模將Compaction分爲了兩類:MinorCompaction和MajorCompaction算法

  • Minor Compaction是指選取一些小的、相鄰的StoreFile將他們合併成一個更大的StoreFile,在這個過程當中不會處理已經Deleted或Expired的Cell。一次Minor Compaction的結果是更少而且更大的StoreFile。
  • Major Compaction是指將全部的StoreFile合併成一個StoreFile,這個過程還會清理三類無心義數據:被刪除的數據、TTL過時數據、版本號超過設定版本號的數據。另外,通常狀況下,Major Compaction時間會持續比較長,整個過程會消耗大量系統資源,對上層業務有比較大的影響。所以線上業務都會將關閉自動觸發Major Compaction功能,改成手動在業務低峯期觸發。

 

Compaction做用 | 反作用

上文提到,隨着hfile文件數不斷增多,一次查詢就可能會須要愈來愈多的IO操做,延遲必然會愈來愈大,以下圖一所示,隨着數據寫入不斷增長,文件數不斷增多,讀取延時也在不斷變大。而執行compaction會使得文件數基本穩定,進而IO Seek次數會比較穩定,延遲就會穩定在必定範圍。然而,compaction操做重寫文件會帶來很大的帶寬壓力以及短期IO壓力。所以能夠認爲,Compaction就是使用短期的IO消耗以及帶寬消耗換取後續查詢的低延遲。從圖上來看,就是延遲有很大的毛刺,但整體趨勢基本穩定不變,見下圖二。性能

爲了換取後續查詢的低延遲,除了短期的讀放大以外,Compaction對寫入也會有很大的影響。 咱們首先假設一個現象:當寫請求很是多,致使不斷生成HFile,但compact的速度遠遠跟不上HFile生成的速度,這樣就會使HFile的數量會愈來愈多,致使讀性能急劇降低。爲了不這種狀況,在HFile的數量過多的時候會限制寫請求的速度:在每次執行MemStore flush的操做前,若是HStore的HFile數超過hbase.hstore.blockingStoreFiles (默認7),則會阻塞flush操做hbase.hstore.blockingWaitTime時間,在這段時間內,若是compact操做使得HStore文件數降低到回這個值,則中止阻塞。另外阻塞超過期間後,也會恢復執行flush操做。這樣作就能夠有效地控制大量寫請求的速度,但同時這也是影響寫請求速度的主要緣由之一。.net

可見,Compaction會使得數據讀取延遲一直比較平穩,但付出的代價是大量的讀延遲毛刺和必定的寫阻塞。線程

Compaction流程

瞭解了必定的背景知識後,接下來須要從全局角度對Compaction進行了解。整個Compaction始於特定的觸發條件,好比flush操做、週期性地Compaction檢查操做等。一旦觸發,HBase會將該Compaction交由一個獨立的線程處理,該線程首先會從對應store中選擇合適的hfile文件進行合併,這一步是整個Compaction的核心,選取文件須要遵循不少條件,好比文件數不能太多、不能太少、文件大小不能太大等等,最理想的狀況是,選取那些承載IO負載重、文件小的文件集,實際實現中,HBase提供了多個文件選取算法:RatioBasedCompactionPolicy、ExploringCompactionPolicy和StripeCompactionPolicy等,用戶也能夠經過特定接口實現本身的Compaction算法;選出待合併的文件後,HBase會根據這些hfile文件總大小挑選對應的線程池處理,最後對這些文件執行具體的合併操做。設計

能夠經過下圖簡單地梳理上述流程:日誌

 

 

觸發時機server

HBase中能夠觸發compaction的因素有不少,最多見的因素有這麼三種:Memstore Flush、後臺線程週期性檢查、手動觸發。blog

1. Memstore Flush: 應該說compaction操做的源頭就來自flush操做,memstore flush會產生HFile文件,文件愈來愈多就須要compact。所以在每次執行完Flush操做以後,都會對當前Store中的文件數進行判斷,一旦文件數# > ,就會觸發compaction。須要說明的是,compaction都是以Store爲單位進行的,而在Flush觸發條件下,整個Region的全部Store都會執行compact,因此會在短期內執行屢次compaction。接口

2. 後臺線程週期性檢查: 後臺線程CompactionChecker按期觸發檢查是否須要執行compaction,檢查週期爲:hbase.server.thread.wakefrequency*hbase.server.compactchecker.interval.multiplier。和flush不一樣的是,該線程優先檢查文件數#是否大於,一旦大於就會觸發compaction。若是不知足,它會接着檢查是否知足major compaction條件,簡單來講,若是當前store中hfile的最先更新時間早於某個值mcTime,就會觸發major compaction,HBase預想經過這種機制按期刪除過時數據。上文mcTime是一個浮動值,浮動區間默認爲[7-7*0.2,7+7*0.2],其中7爲hbase.hregion.majorcompaction,0.2爲hbase.hregion.majorcompaction.jitter,可見默認在7天左右就會執行一次major compaction。用戶若是想禁用major compaction,只須要將參數hbase.hregion.majorcompaction設爲0ip

3. 手動觸發:通常來說,手動觸發compaction一般是爲了執行major compaction,緣由有三,其一是由於不少業務擔憂自動major compaction影響讀寫性能,所以會選擇低峯期手動觸發;其二也有多是用戶在執行完alter操做以後但願馬上生效,執行手動觸發major compaction;其三是HBase管理員發現硬盤容量不夠的狀況下手動觸發major compaction刪除大量過時數據;不管哪一種觸發動機,一旦手動觸發,HBase會不作不少自動化檢查,直接執行合併。

 

 

挑選合適的線程池

HBase實現中有一個專門的線程CompactSplitThead負責接收compact請求以及split請求,並且爲了可以獨立處理這些請求,這個線程內部構造了多個線程池:largeCompactions、smallCompactions以及splits等,其中splits線程池負責處理全部的split請求,largeCompactions和smallCompaction負責處理全部的compaction請求,其中前者用來處理大規模compaction,後者處理小規模compaction。這裏須要明白三點:

1. 上述設計目的是爲了可以將請求獨立處理,提供系統的處理性能。

2. 哪些compaction應該分配給largeCompactions處理,哪些應該分配給smallCompactions處理?是否是Major Compaction就應該交給largeCompactions線程池處理?不對。這裏有個分配原則:待compact的文件總大小若是大於值throttlePoint(能夠經過參數 hbase.hregion.majorcompaction配置, 默認爲2.5G),分配給largeCompactions處理,不然分配給smallCompactions處理。

3. largeCompactions線程池和smallCompactions線程池默認都只有一個線程,用戶能夠經過參數 hbase.regionserver.thread.compaction.large和hbase.regionserver.thread.compaction.small進行配置

 

 

選擇合適HFile合併

選擇合適的文件進行合併是整個compaction的核心,由於合併文件的大小以及其當前承載的IO數直接決定了compaction的效果。最理想的狀況是,這些文件承載了大量IO請求可是大小很小,這樣compaction自己不會消耗太多IO,並且合併完成以後對讀的性能會有顯著提高。然而現實狀況可能大部分都不會是這樣,在0.96版本和0.98版本,分別提出了兩種選擇策略,在充分考慮總體狀況的基礎上選擇最佳方案。不管哪一種選擇策略,都會首先對該Store中全部HFile進行一一排查,排除不知足條件的部分文件:

1. 排除當前正在執行compact的文件及其比這些文件更新的全部文件(SequenceId更大)

2. 排除某些過大的單個文件,若是文件大小大於hbase.hzstore.compaction.max.size( 默認Long最大值 ),則被排除,不然會產生大量IO消耗

通過排除的文件稱爲候選文件,HBase接下來會再判斷是否知足major compaction條件,若是知足,就會選擇所有文件進行合併。判斷條件有下面三條,只要知足其中一條就會執行major compaction:

1. 用戶強制執行major compaction

2.  長時間沒有進行compact(CompactionChecker的判斷條件2)且候選文件數小於hbase.hstore.compaction.max(默認10)

3. Store中含有Reference文件,Reference文件是split region產生的臨時文件,只是簡單的引用文件,通常必須在compact過程當中刪除

若是不知足major compaction條件,就必然爲minor compaction,HBase主要有兩種minor策略:RatioBasedCompactionPolicy和ExploringCompactionPolicy,下面分別進行介紹:

RatioBasedCompactionPolicy

從老到新逐一掃描全部候選文件,知足其中條件之一便中止掃描:

(1)當前文件大小 < 比它更新的全部文件大小總和 * ratio,其中ratio是一個可變的比例,在高峯期時ratio爲1.2,非高峯期爲5,也就是非高峯期容許compact更大的文件。那何時是高峯期,何時是非高峯期呢?用戶能夠配置參數hbase.offpeak.start.hour和hbase.offpeak.end.hour來設置高峯期

(2)當前所剩候選文件數 <= hbase.store.compaction.min(默認爲3)

中止掃描後,待合併文件就選擇出來了,即爲當前掃描文件+比它更新的全部文件

ExploringCompactionPolicy

該策略思路基本和RatioBasedCompactionPolicy相同,不一樣的是,Ratio策略在找到一個合適的文件集合以後就中止掃描了,而Exploring策略會記錄下全部合適的文件集合,並在這些文件集合中尋找最優解。最優解能夠理解爲:待合併文件數最多或者待合併文件數相同的狀況下文件大小較小,這樣有利於減小compaction帶來的IO消耗。具體流程戳 這裏

須要注意的是,Ratio策略是0.94版本的默認策略,而0.96版本以後默認策略就換爲了Exploring策略,在cloudera博文 《what-are-hbase-compactions》 中,做者給出了一個二者的簡單性能對比,基本能夠看出後者在節省IO方面會有10%左右的提高:

 

 

 

執行HFile文件合併

上文一方面選出了待合併的HFile集合,一方面也選出來了合適的處理線程,萬事俱備,只欠最後真正的合併。合併流程提及來也簡單,主要分爲以下幾步:

1. 分別讀出待合併hfile文件的KV,並順序寫到位於./tmp目錄下的臨時文件中

2. 將臨時文件移動到對應region的數據目錄

3. 將compaction的輸入文件路徑和輸出文件路徑封裝爲KV寫入WAL日誌,並打上compaction標記,最後強制執行sync

4. 將對應region數據目錄下的compaction輸入文件所有刪除

上述四個步驟看起來簡單,但實際是很嚴謹的,具備很強的容錯性和完美的冪等性:

1. 若是RS在步驟2以前發生異常,本次compaction會被認爲失敗,若是繼續進行一樣的compaction,上次異常對接下來的compaction不會有任何影響,也不會對讀寫有任何影響。惟一的影響就是多了一份多餘的數據。

2. 若是RS在步驟2以後、步驟3以前發生異常,一樣的,僅僅會多一份冗餘數據。

3. 若是在步驟3以後、步驟4以前發生異常,RS在從新打開region以後首先會從WAL中看到標有compaction的日誌,由於此時輸入文件和輸出文件已經持久化到HDFS,所以只須要根據WAL移除掉compaction輸入文件便可

相關文章
相關標籤/搜索