HBASE 技術細節 寫入 Region拆分與合併介紹

1. Hbase的Region介紹

Hbase Rowkey CF 架構 概述 預分區及Rowkey設計 學習筆記介紹了Region類似於數據庫的分片和分區的概念,每個Region負責一小部分Rowkey範圍的數據的讀寫和維護,Region包含了對應的起始行到結束行的所有信息。master將對應的region分配給不同的RergionServer,由RegionSever來提供Region的讀寫服務和相關的管理工作。

這部分主要介紹Region實例以及Region的尋找路徑:

1.1 region實例

img

上圖模擬了一個Hbase的表是如何拆分成region,以及分配到不同的RegionServer中去。上面是1個Userinfo表,裏面有7條記錄,其中rowkey爲0001到0002的記錄被分配到了Region1上,Rowkey爲0003到0004的記錄被分配到了Region2上,而rowkey爲0005、0006和0007的記錄則被分配到了Region3上。region1和region2被master分配給了RegionServer1(RS1),Region3被master配分給了RegionServer2(RS2)

備註:這裏只是爲了更容易的說明拆分的規則,其實真實的場景並不會幾條記錄拆分到不通的Region上,而是到一定的數據量纔會拆分,具體的在Region的拆分那部分再具體的介紹。

1.2 Region的尋址

既然讀寫都在RegionServer上發生,我們前面有講到,每個RegionSever爲一定數量的region服務,那麼client要對某一行數據做讀寫的時候如何能知道具體要去訪問哪個RegionServer呢?那就是接下來我們要討論的問題

1.2.1 老的Region尋址方式

在Hbase 0.96版本以前,Hbase有兩個特殊的表,分別是-ROOT-表和.META.表,其中-ROOT-的位置存儲在ZooKeeper中,-ROOT-本身存儲了 .META. Table的RegionInfo信息,並且-ROOT-不會分裂,只有一個region。而.META.表可以被切分成多個region。讀取的流程如下圖所示:

img

  • 第1步:client請求ZK獲得-ROOT-所在的RegionServer地址

  • 第2步:client請求-ROOT-所在的RS地址,獲取.META.表的地址,client會將-ROOT-的相關信息cache下來,以便下一次快速訪問

  • 第3步:client請求 .META.表的RS地址,獲取訪問數據所在RegionServer的地址,client會將.META.的相關信息cache下來,以便下一次快速訪問

  • 第4步:client請求訪問數據所在RegionServer的地址,獲取對應的數據

從上面的路徑我們可以看出,用戶需要3次請求才能直到用戶Table真正的位置,這在一定程序帶來了性能的下降。在0.96之前使用3層設計的主要原因是考慮到元數據可能需要很大。但是真正集羣運行,元數據的大小其實很容易計算出來。在BigTable的論文中,每行METADATA數據存儲大小爲1KB左右,如果按照一個Region爲128M的計算,3層設計可以支持的Region個數爲234個,採用2層設計可以支持217(131072)。那麼2層設計的情況下一個 集羣可以存儲4P的數據。這僅僅是一個Region只有128M的情況下。如果是10G呢? 因此,通過計算,其實2層設計就可以滿足集羣的需求。因此在0.96版本以後就去掉了-ROOT-表了。

1.2.2 新的Region尋址方式

如上面的計算,2層結構其實完全能滿足業務的需求,因此0.96版本以後將-ROOT-表去掉了。如下圖所示:

img

訪問路徑變成了3步:

  • 第1步:Client請求ZK獲取.META.所在的RegionServer的地址。

  • 第2步:Client請求.META.所在的RegionServer獲取訪問數據所在的RegionServer地址,client會將.META.的相關信息cache下來,以便下一次快速訪問。

  • 第3步:Client請求數據所在的RegionServer,獲取所需要的數據。

總結去掉-ROOT-的原因有如下2點:

其一:提高性能

其二:2層結構已經足以滿足集羣的需求

這裏還有一個問題需要說明,那就是Client會緩存.META.的數據,用來加快訪問,既然有緩存,那它什麼時候更新?如果.META.更新了,比如Region1不在RerverServer2上了,被轉移到了RerverServer3上。client的緩存沒有更新會有什麼情況? 其實,Client的元數據緩存不更新,當.META.的數據發生更新。如上面的例子,由於Region1的位置發生了變化,Client再次根據緩存去訪問的時候,會出現錯誤,當出現異常達到重試次數後就會去.META.所在的RegionServer獲取最新的數據,如果.META.所在的RegionServer也變了,Client就會去ZK上獲取.META.所在的RegionServer的最新地址。

2. Hbase的寫邏輯

Hbase的寫邏輯涉及到寫內存、寫log、刷盤等操作,看起來簡單,其實裏面又有很多的邏輯,下面就來做詳細的介紹。

2.1 Hbase寫入邏輯

Hbase的寫入流程如下圖所示:

img

從上圖可以看出分爲3步驟:

第1步:Client獲取數據寫入的Region所在的RegionServer

第2步:請求寫Hlog

第3步:請求寫MemStore

只有當寫Hlog和寫MemStore都成功了纔算請求寫入完成。MemStore後續會逐漸刷到HDFS中。

備註:Hlog存儲在HDFS,當RegionServer出現異常,需要使用Hlog來恢復數據。

2.2 MemStore刷盤

爲了提高Hbase的寫入性能,當寫請求寫入MemStore後,不會立即刷盤。而是會等到一定的時候進行刷盤的操作。具體是哪些場景會觸發刷盤的操作呢?總結成如下的幾個場景:

2.2.1 全局內存控制

這個全局的參數是控制內存整體的使用情況,當所有memstore佔整個heap的最大比例的時候,會觸發刷盤的操作。這個參數是hbase.regionserver.global.memstore.upperLimit,默認爲整個heap內存的40%。但這並不意味着全局內存觸發的刷盤操作會將所有的MemStore都進行輸盤,而是通過另外一個參數hbase.regionserver.global.memstore.lowerLimit來控制,默認是整個heap內存的35%。當flush到所有memstore佔整個heap內存的比率爲35%的時候,就停止刷盤。這麼做主要是爲了減少刷盤對業務帶來的影響,實現平滑系統負載的目的。

2.2.2 MemStore達到上限

當MemStore的大小達到hbase.hregion.memstore.flush.size大小的時候會觸發刷盤,默認128M大小

2.2.3 RegionServer的Hlog數量達到上限

前面說到Hlog爲了保證Hbase數據的一致性,那麼如果Hlog太多的話,會導致故障恢復的時間太長,因此Hbase會對Hlog的最大個數做限制。當達到Hlog的最大個數的時候,會強制刷盤。這個參數是hase.regionserver.max.logs,默認是32個。

2.2.4 手工觸發

可以通過hbase shell或者java api手工觸發flush的操作。

2.2.5 關閉RegionServer觸發

在正常關閉RegionServer會觸發刷盤的操作,全部數據刷盤後就不需要再使用Hlog恢復數據。

2.2.6 Region使用HLOG恢復完數據後觸發

當RegionServer出現故障的時候,其上面的Region會遷移到其他正常的RegionServer上,在恢復完Region的數據後,會觸發刷盤,當刷盤完成後纔會提供給業務訪問。

2.3 Hlog

2.3.1 Hlog簡介

Hlog是Hbase實現WAL(Write ahead log)方式產生的日誌信息,內部是一個簡單的順序日誌。每個RegionServer對應1個Hlog(備註:1.x版本的可以開啓MultiWAL功能,允許多個Hlog),所有對於該RegionServer的寫入都被記錄到Hlog中。Hlog實現的功能是保證數據安全。當RegionServer出現問題的時候,能跟進Hlog來做數據恢復。此外爲了保證恢復的效率,Hbase會限制最大保存的Hlog數量,如果達到Hlog的最大個數(hbase.regionserver.max.logs參數控制)的時候,就會觸發強制刷盤操作。對於已經刷盤的數據,其對應的Hlog會有一個過期的概念,Hlog過期後,會被監控線程移動到 .oldlogs,然後會被自動刪除掉。

Hbase是如何判斷Hlog過期的呢?要找到這個答案,我們就必須瞭解Hlog的詳細結構。

2.3.2 Hlog結構

下圖是Hlog的詳細結構(圖片來源 http://hbasefly.com/ ):

img

從上圖我們可以看出都個Region共享一個Hlog文件,單個Region在Hlog中是按照時間順序存儲的,但是多個Region可能並不是完全按照時間順序。

每個Hlog最小單元由Hlogkey和WALEdit兩部分組成。Hlogky由sequenceid、timestamp、cluster ids、regionname以及tablename等組成,WALEdit是由一系列的KeyValue組成,對一行上所有列(即所有KeyValue)的更新操作,都包含在同一個WALEdit對象中,這主要是爲了實現寫入一行多個列時的原子性。

注意,圖中有個sequenceid的東東。sequenceid是一個store級別的自增序列號,這東東非常重要,region的數據恢復和Hlog過期清除都要依賴這個東東。下面就來簡單描述一下sequenceid的相關邏輯。

  • Memstore在達到一定的條件會觸發刷盤的操作,刷盤的時候會獲取刷新到最新的一個sequenceid的下一個sequenceid,並將新的sequenceid賦給oldestUnflushedSequenceId,並刷到Ffile中。有點繞,舉個例子來說明:比如對於某一個store,開始的時候oldestUnflushedSequenceId爲NULL,此時,如果觸發flush的操作,假設初始刷盤到sequenceid爲10,那麼hbase會在10的基礎上append一個空的Entry到HLog,最新的sequenceid爲11,然後將sequenceid爲11的號賦給oldestUnflushedSequenceId,並將oldestUnflushedSequenceId的值刷到Hfile文件中進行持久化。
  • Hlog文件對應所有Region的store中最大的sequenceid如果已經刷盤,就認爲Hlog文件已經過期,就會移動到.oldlogs,等待被移除。
  • 當RegionServer出現故障的時候,需要對Hlog進行回放來恢復數據。回放的時候會讀取Hfile的oldestUnflushedSequenceId中的sequenceid和Hlog中的sequenceid進行比較,小於sequenceid的就直接忽略,但與或者等於的就進行重做。回放完成後,就完成了數據的恢復工作。

2.3.3 Hlog的生命週期

Hlog從產生到最後刪除需要經歷如下幾個過程:

  • 產生 所有涉及到數據的變更都會先寫Hlog,除非是你關閉了Hlog
  • 滾動 Hlog的大小通過參數hbase.regionserver.logroll.period控制,默認是1個小時,時間達到hbase.regionserver.logroll.period 設置的時間,Hbase會創建一個新的Hlog文件。這就實現了Hlog滾動的目的。Hbase通過hbase.regionserver.maxlogs參數控制Hlog的個數。滾動的目的,爲了控制單個Hlog文件過大的情況,方便後續的過期和刪除。
  • 過期 前面我們有講到sequenceid這個東東,Hlog的過期依賴於對sequenceid的判斷。Hbase會將Hlog的sequenceid和Hfile最大的sequenceid(刷新到的最新位置)進行比較,如果該Hlog文件中的sequenceid比刷新的最新位置的sequenceid都要小,那麼這個Hlog就過期了,過期了以後,對應Hlog會被移動到.oldlogs目錄。 這裏有個問題,爲什麼要將過期的Hlog移動到.oldlogs目錄,而不是直接刪除呢? 答案是因爲Hbase還有一個主從同步的功能,這個依賴Hlog來同步Hbase的變更,有一種情況不能刪除Hlog,那就是Hlog雖然過期,但是對應的Hlog並沒有同步完成,因此比較好的做好是移動到別的目錄。再增加對應的檢查和保留時間。
  • 刪除 如果Hbase開啓了replication,當replication執行完一個Hlog的時候,會刪除Zoopkeeper上的對應Hlog節點。在Hlog被移動到.oldlogs目錄後,Hbase每隔hbase.master.cleaner.interval(默認60秒)時間會去檢查.oldlogs目錄下的所有Hlog,確認對應的Zookeeper的Hlog節點是否被刪除,如果Zookeeper 上不存在對應的Hlog節點,那麼就直接刪除對應的Hlog。 hbase.master.logcleaner.ttl(默認10分鐘)這個參數設置Hlog在.oldlogs目錄保留的最長時間。

3. RegionServer的故障恢復

我們知道,RegionServer的相關信息保存在ZK中,在RegionServer啓動的時候,會在Zookeeper中創建對應的臨時節點。RegionServer通過Socket和Zookeeper建立session會話,RegionServer會週期性地向Zookeeper發送ping消息包,以此說明自己還處於存活狀態。而Zookeeper收到ping包後,則會更新對應session的超時時間。

當Zookeeper超過session超時時間還未收到RegionServer的ping包,則Zookeeper會認爲該RegionServer出現故障,ZK會將該RegionServer對應的臨時節點刪除,並通知Master,Master收到RegionServer掛掉的信息後就會啓動數據恢復的流程。

Master啓動數據恢復流程後,其實主要的流程如下:

RegionServer宕機—》ZK檢測到RegionServer異常—》Master啓動數據恢復—》Hlog切分—》Region重新分配—》Hlog重放—》恢復完成並提供服務

故障恢復有3中模式,下面就一一來介紹。

3.1 LogSplitting

在最開始的恢復流程中,Hlog的整個切分過程都由於Master來執行,如下圖所示:

img

  • a、將待切分的日誌文件夾進行重命名,防止RegionServer未真的宕機而持續寫入Hlog
  • b、Master啓動讀取線程讀取Hlog的數據,並將不同RegionServer的日誌寫入到不通的內存buffer中
  • c、針對每個buffer,Master會啓動對應的寫線程將不同Region的buffer數據寫入到HDFS中,對應的路徑爲/hbase/table_name/region/recoverd.edits/.tmp。
  • d、Master重新將宕機的RegionServer中的Rgion分配到正常的RegionServer中,對應的RegionServer讀取Region的數據,會發現該region目錄下的recoverd.edits目錄以及相關的日誌,然後RegionServer重放對應的Hlog日誌,從而實現對應Region數據的恢復。 從上面的步驟中,我們可以看出Hlog的切分一直都是master在幹活,效率比較低。設想,如果集羣中有多臺RegionServer在同一時間宕機,會是什麼情況?串行修復,肯定異常慢,因爲只有master一個人在幹Hlog切分的活。因此,爲了提高效率,開發了Distributed Log Splitting架構。

3.2 Distributed Log Splitting

顧名思義,Distributed Log Splitting是LogSplitting的分佈式實現,分佈式就不是master一個人在幹活了,而是充分使用各個RegionServer上的資源,利用多個RegionServer來並行切分Hlog,提高切分的效率。如下圖所示:

img

上圖的操作順序如下:

  • a、Master將要切分的日誌發佈到Zookeeper節點上(/hbase/splitWAL),每個Hlog日誌一個任務,任務的初始狀態爲TASK_UNASSIGNED
  • b、在Master發佈Hlog任務後,RegionServer會採用競爭方式認領對應的任務(先查看任務的狀態,如果是TASK_UNASSIGNED,就將該任務狀態修改爲TASK_OWNED)
  • c、RegionServer取得任務後會讓對應的HLogSplitter線程處理Hlog的切分,切分的時候讀取出Hlog的對,然後寫入不通的Region buffer的內存中。
  • d、RegionServer啓動對應寫線程,將Region buffer的數據寫入到HDFS中,路徑爲/hbase/table/region/seqenceid.temp,seqenceid是一個日誌中該Region對應的最大sequenceid,如果日誌切分成功,而RegionServer會將對應的ZK節點的任務修改爲TASK_DONE,如果切分失敗,則會將任務修改爲TASK_ERR。
  • e、如果任務是TASK_ERR狀態,則Master會重新發布該任務,繼續由RegionServer競爭任務,並做切分處理。
  • f、Master重新將宕機的RegionServer中的Rgion分配到正常的RegionServer中,對應的RegionServer讀取Region的數據,將該region目錄下的一系列的seqenceid.temp進行從小到大進行重放,從而實現對應Region數據的恢復。

從上面的步驟中,我們可以看出Distributed Log Splitting採用分佈式的方式,使用多臺RegionServer做Hlog的切分工作,確實能提高效率。正常故障恢復可以降低到分鐘級別。

但是這種方式有個弊端是會產生很多小文件(切分的Hlog數 宕機的RegionServer上的Region數)。比如一個RegionServer有20個Region,有50個Hlog,那麼產生的小文件數量爲2050=1000個。如果集羣中有多臺RegionServer宕機的情況,小文件更是會成倍增加,恢復的過程還是會比較慢。由次誕生了Distributed Log Replay模式。

3.3 Distributed Log Replay

Distributed Log Replay和Distributed Log Splitting的不同是先將宕機RegionServer上的Region分配給正常的RgionServer,並將該Region標記爲recovering。再使用Distributed Log Splitting類似的方式進行Hlog切分,不同的是,RegionServer將Hlog切分到對應Region buffer後,並不寫HDFS,而是直接進行重放。這樣可以減少將大量的文件寫入HDFS中,大大減少了HDFS的IO消耗。如下圖所示:

img

4. Region的拆分

在HBase中,split其實是進行sharding的一種技術手段,通過HBase的split條件和split策略,將region進行合理的split,再通過HBase的balance策略,將分裂的region負載均衡到各個regionserver上,最大化的發揮分佈式系統的優點。HBase這種自動的sharding技術比傳統的數據庫sharding要省事的多,減輕了維護的成本,但是這樣也會給HBase帶來額外的IO開銷,因此在很多系統中如果能很好的預計rowkey的分佈和數據增長情況,可以通過預先分區,事先將region分配好,再將HBase的自動分區禁掉。

4.1 Hbase Region的拆分觸發條件及三種策略

HBase Split觸發條件

  1. Pre-splitting
    當一個table剛被創建的時候,Hbase默認的分配一個region給table。也就是說這個時候,所有的讀寫請求都會訪問到同一個regionServer的同一個region中,這個時候就達不到負載均衡的效果了,集羣中的其他regionServer就可能會處於比較空閒的狀態。解決這個問題可以用pre-splitting,在創建table的時候就配置好,生成多個region。

    在table初始化的時候如果不配置的話,Hbase是不知道如何去split region的,因爲Hbase不知道應該那個row key可以作爲split的開始點。如果我們可以大概預測到row key的分佈,我們可以使用pre-spliting來幫助我們提前split region。不過如果我們預測得不準確的話,還是可能導致某個region過熱,被集中訪問,不過還好我們還有auto-split。最好的辦法就是首先預測split的切分點,做pre-splitting,然後後面讓auto-split來處理後面的負載均衡。

    Hbase自帶了兩種pre-split的算法,分別是 HexStringSplit 和 UniformSplit 。如果我們的row key是十六進制的字符串作爲前綴的,就比較適合用HexStringSplit,作爲pre-split的算法。例如,我們使用HexHash(prefix)作爲row key的前綴,其中Hexhash爲最終得到十六進制字符串的hash算法。我們也可以用我們自己的split算法。

  2. Auto splitting

    1. 當memstore flush操作後,HRegion寫入新的HFile,有可能產生較大的HFile,HBase就會調用CompactSplitThread.requestSplit判斷是否需要split操作。

    2. HStore剛剛進行完compact操作後有可能產生較大的HFile,當滿足HBase的某一分裂策略後就會進行split操作。

    Hbase Region的拆分策略有比較多,比如除了3種默認過的策略,還有DelimitedKeyPrefixRegionSplitPolicy、KeyPrefixRegionSplitPolicy、DisableSplitPolicy等策略,這裏只介紹3種默認的策略。分別是ConstantSizeRegionSplitPolicy策略、IncreasingToUpperBoundRegionSplitPolicy策略和SteppingSplitPolicy策略。

4.1.1、ConstantSizeRegionSplitPolicy

ConstantSizeRegionSplitPolicy策略是0.94版本之前的默認拆分策略,這個策略的拆分規則是:當region大小達到hbase.hregion.max.filesize(默認10G)後拆分。 這種拆分策略對於小表不太友好,按照默認的設置,如果1個表的Hfile小於10G就一直不會拆分。注意10G是壓縮後的大小,如果使用了壓縮的話。

如果1個表一直不拆分,訪問量小也不會有問題,但是如果這個表訪問量比較大的話,就比較容易出現性能問題。這個時候只能手工進行拆分。還是很不方便。

4.1.2 IncreasingToUpperBoundRegionSplitPolicy

IncreasingToUpperBoundRegionSplitPolicy策略是Hbase的0.94~2.0版本默認的拆分策略,這個策略相較於ConstantSizeRegionSplitPolicy策略做了一些優化,該策略的算法爲:min(r^2*flushSize,maxFileSize ),最大爲maxFileSize 。

從這個算是我們可以得出flushsize爲128M、maxFileSize爲10G的情況下,可以計算出Region的分裂情況如下: 第一次拆分大小爲:min(10G,11128M)=128M 第二次拆分大小爲:min(10G,3*3*128M)=1152M 第三次拆分大小爲:min(10G,55128M)=3200M 第四次拆分大小爲:min(10G,77128M)=6272M 第五次拆分大小爲:min(10G,9*9*128M)=10G 第五次拆分大小爲:min(10G,11*11*128M)=10G

從上面的計算我們可以看到這種策略能夠自適應大表和小表,但是這種策略會導致小表產生比較多的小region,對於小表還是不是很完美。

4.1.3 SteppingSplitPolicy

SteppingSplitPolicy是在Hbase 2.0版本後的默認策略,,拆分規則爲:If region=1 then: flush size * 2 else: MaxRegionFileSize。

還是以flushsize爲128M、maxFileSize爲10場景爲列,計算出Region的分裂情況如下: 第一次拆分大小爲:2*128M=256M 第二次拆分大小爲:10G

從上面的計算我們可以看出,這種策略兼顧了ConstantSizeRegionSplitPolicy策略和IncreasingToUpperBoundRegionSplitPolicy策略,對於小表也肯呢個比較好的適配。

4.2 Hbase Region拆分的詳細流程

Hbase的詳細拆分流程圖如下:

img

備註圖片來源( https://zh.hortonworks.com/blog/apache-hbase-region-splitting-and-merging/ )

從上圖我們可以看出Region切分的詳細流程如下:

  1. 會ZK的/hbase/region-in-transition/region-name下創建一個znode,並設置狀態爲SPLITTING

  2. master通過watch節點檢測到Region狀態的變化,並修改內存中Region狀態的變化

  3. RegionServer在父Region的目錄下創建一個名稱爲.splits的子目錄

  4. RegionServer關閉父Region,強制將數據刷新到磁盤,並這個Region標記爲offline的狀態。此時,落到這個Region的請求都會返回NotServingRegionException這個錯誤

  5. RegionServer在.splits創建daughterA和daughterB,並在文件夾中創建對應的reference文件,指向父Region的Region文件

  6. RegionServer在HDFS中創建daughterA和daughterB的Region目錄,並將reference文件移動到對應的Region目錄中

  7. 在.META.表中設置父Region爲offline狀態,不再提供服務,並將父Region的daughterA和daughterB的Region添加到.META.表中,已表名父Region被拆分成了daughterA和daughterB兩個Region

  8. RegionServer並行開啓兩個子Region,並正式提供對外寫服務

  9. RegionSever將daughterA和daughterB添加到.META.表中,這樣就可以從.META.找到子Region,並可以對子Region進行訪問了

  10. RegionServr修改/hbase/region-in-transition/region-name的znode的狀態爲SPLIT

備註:爲了減少對業務的影響,Region的拆分並不涉及到數據遷移的操作,而只是創建了對父Region的指向。只有在做大合併的時候,纔會將數據進行遷移。

那麼通過reference文件如何才能查找到對應的數據呢?如下圖所示:

img

  • 根據文件名來判斷是否是reference文件
  • 由於reference文件的命名規則爲前半部分爲父Region對應的File的文件名,後半部分是父Region的名稱,因此讀取的時候也根據前半部分和後半部分來識別
  • 根據reference文件的內容來確定掃描的範圍,reference的內容包含兩部分,一部分是切分點splitkey,另一部分是boolean類型的變量(true或者false)。如果爲true則掃描文件的上半部分,false則掃描文件的下半部分
  • 接下來確定了掃描的文件,以及文件的掃描範圍,那就按照正常的文件檢索了

5. Region的合併

熟悉HBase的同學應該知道,HBase是基於一種LSM-Tree(Log-Structured Merge Tree)存儲模型設計的,寫入路徑上是先寫入WAL(Write-Ahead-Log)即預寫日誌,再寫入memstore緩存,滿足一定條件後執行flush操作將緩存數據刷寫到磁盤,生成一個HFile數據文件。隨着數據不斷寫入,磁盤HFile文件就會越來越多,文件太多會影響HBase查詢性能,主要體現在查詢數據的io次數增加。爲了優化查詢性能,HBase會合並小的HFile以減少文件數量,這種合併HFile的操作稱爲Compaction,這也是爲什麼要進行Compaction的主要原因。

HBase觸發Compaction的條件有三種:memstore Flush、後臺線程週期性檢查、手動觸發。

  • memstore flush:可以說compaction的根源就在於flush,memstore 達到一定閾值或其他條件時就會觸發flush刷寫到磁盤生成HFile文件,正是因爲HFile文件越來越多才需要compact。HBase每次flush之後,都會判斷是否要進行compaction,一旦滿足minor compaction或major compaction的條件便會觸發執行。

  • 後臺線程週期性檢查: 後臺線程 CompactionChecker 會定期檢查是否需要執行compaction,檢查週期爲hbase.server.thread.wakefrequency * hbase.server.compactchecker.interval.multiplier,這裏主要考慮的是一段時間內沒有寫入請求仍然需要做compact檢查。其中參數 hbase.server.thread.wakefrequency 默認值 10000 即 10s,是HBase服務端線程喚醒時間間隔,用於log roller、memstore flusher等操作週期性檢查;參數 hbase.server.compactchecker.interval.multiplier 默認值1000,是compaction操作週期性檢查乘數因子。10 * 1000 s 時間上約等於2hrs, 46mins, 40sec。

  • 手動觸發:是指通過HBase Shell、Master UI界面或者HBase API等任一種方式 執行 compact、major_compact等命令。

Region的合併分爲小合併和大合併,下面就分別來做介紹:

5.1 小合併(MinorCompaction)

由前面的刷盤部分的介紹,我們知道當MemStore達到hbase.hregion.memstore.flush.size大小的時候會將數據刷到磁盤,生產StoreFile,因此勢必產生很多的小問題,對於Hbase的讀取,如果要掃描大量的小文件,會導致性能很差,因此需要將這些小文件合併成大一點的文件。因此所謂的小合併,就是把多個小的StoreFile組合在一起,形成一個較大的StoreFile,通常是累積到3個Store File後執行。通過參數hbase.hstore,compactionThreadhold配置。小合併的大致步驟爲:

  • 分別讀取出待合併的StoreFile文件的KeyValues,並順序地寫入到位於./tmp目錄下的臨時文件中
  • 將臨時文件移動到對應的Region目錄中
  • 將合併的輸入文件路徑和輸出路徑封裝成KeyValues寫入WAL日誌,並打上compaction標記,最後強制自行sync
  • 將對應region數據目錄下的合併的輸入文件全部刪除,合併完成

這種小合併一般速度很快,對業務的影響也比較小。本質上,小合併就是使用短時間的IO消耗以及帶寬消耗換取後續查詢的低延遲。

5.2 大合併(MajorCompaction)

所謂的大合併,就是將一個Region下的所有StoreFile合併成一個StoreFile文件,在大合併的過程中,之前刪除的行和過期的版本都會被刪除,拆分的母Region的數據也會遷移到拆分後的子Region上。大合併一般一週做一次,控制參數爲hbase.hregion.majorcompaction。大合併的影響一般比較大,儘量避免統一時間多個Region進行合併,因此Hbase通過一些參數來進行控制,用於防止多個Region同時進行大合併。該參數爲: hbase.hregion.majorcompaction.jitter 具體算法爲:

hbase.hregion.majorcompaction參數的值乘於一個隨機分數,這個隨機分數不能超過hbase.hregion.majorcompaction.jitter的值。hbase.hregion.majorcompaction.jitter的值默認爲0.5。 通過hbase.hregion.majorcompaction參數的值加上或減去hbase.hregion.majorcompaction參數的值乘於一個隨機分數的值就確定下一次大合併的時間區間。

用戶如果想禁用major compaction,只需要將參數hbase.hregion.majorcompaction設爲0。建議禁用。

Ref

  1. https://cloud.tencent.com/developer/article/1006043
  2. https://cloud.tencent.com/developer/article/1006044
  3. https://blog.csdn.net/u010039929/article/details/74295869
  4. https://blog.csdn.net/u011598442/article/details/90632702
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章