1. 程式人生 > >Hbase技術詳細學習筆記

Hbase技術詳細學習筆記

最近在逐步跟進Hbase的相關工作,由於之前對Hbase並不怎麼了解,因此係統地學習了下Hbase,為了加深對Hbase的理解,對相關知識點做了筆記,並在組內進行了Hbase相關技術的分享,由於Hbase涵蓋的內容比較多,因此計劃分享2期,下面就是針對第一期Hbase技術分享整體而成,第一期的主要內容如下:

一、Hbase介紹

二、Hbase的Region介紹

三、Hbase的寫邏輯介紹

四、Hbase的故障恢復

五、Hbase的拆分和合並

如下ppt所示:

下面就來針對各個部分的內容來進行詳細的介紹:

一、Hbase介紹

1、Hbase簡介

Hbase是Hadoop Database的簡稱 ,Hbase專案是由Powerset公司的Chad Walters和Jim Kelleman在2006年末發起,根據Google的Chang等人發表的論文“Bigtable:A Distributed Storage System for Strctured Data“來設計的。2007年10月釋出了第一個版本。2010年5月,Hbase從Hadoop子專案升級成Apache頂級專案。

Hbase是分散式、面向列的開源資料庫(其實準確的說是面向列族)。HDFS為Hbase提供可靠的底層資料儲存服務,MapReduce為Hbase提供高效能的計算能力,Zookeeper為Hbase提供穩定服務和Failover機制,因此我們說Hbase是一個通過大量廉價的機器解決海量資料的高速儲存和讀取的分散式資料庫解決方案。

2、Hbase幾個特點介紹

提煉出Hbase的幾個特點,如下圖所示:

2.1、海量儲存

Hbase適合儲存PB級別的海量資料,在PB級別的資料以及採用廉價PC儲存的情況下,能在幾十到百毫秒內返回資料。這與Hbase的極易擴充套件性息息相關。正式因為Hbase良好的擴充套件性,才為海量資料的儲存提供了便利。

2.2、列式儲存

這裡的列式儲存其實說的是列族儲存,Hbase是根據列族來儲存資料的。列族下面可以有非常多的列,列族在建立表的時候就必須指定。為了加深對Hbase列族的理解,下面是一個簡單的關係型資料庫的表和Hbase資料庫的表:

RDBMS的表:

Hbase的表:

下圖是針對Hbase和關係型資料庫的基本的一個比較:

2.3、極易擴充套件

Hbase的擴充套件性主要體現在兩個方面,一個是基於上層處理能力(RegionServer)的擴充套件,一個是基於儲存的擴充套件(HDFS)。

通過橫向新增RegionSever的機器,進行水平擴充套件,提升Hbase上層的處理能力,提升Hbsae服務更多Region的能力。

備註:RegionServer的作用是管理region、承接業務的訪問,這個後面會詳細的介紹

通過橫向新增Datanode的機器,進行儲存層擴容,提升Hbase的資料儲存能力和提升後端儲存的讀寫能力。

2.4、高併發

由於目前大部分使用Hbase的架構,都是採用的廉價PC,因此單個IO的延遲其實並不小,一般在幾十到上百ms之間。這裡說的高併發,主要是在併發的情況下,Hbase的單個IO延遲下降並不多。能獲得高併發、低延遲的服務。

2.5、稀疏

稀疏主要是針對Hbase列的靈活性,在列族中,你可以指定任意多的列,在列資料為空的情況下,是不會佔用儲存空間的。

3、Hbase的幾個概念介紹

在我學習Hbase的時候有幾個概念需要重點理解一下,列出4個基礎概念如下圖所示:

2.1、Column Family的概念

Column Family又叫列族,Hbase通過列族劃分資料的儲存,列族下面可以包含任意多的列,實現靈活的資料存取。剛接觸的時候,理解起來有點吃力。我想到了一個非常類似的概念,理解起來就非常容易了。那就是家族的概念,我們知道一個家族是由於很多個的家庭組成的。列族也類似,列族是由一個一個的列組成(任意多)。

Hbase表的建立的時候就必須指定列族。就像關係型資料庫建立的時候必須指定具體的列是一樣的。

Hbase的列族不是越多越好,官方推薦的是列族最好小於或者等於3。我們使用的場景一般是1個列族。

2.2、Rowkey的概念

Rowkey的概念和mysql中的主鍵是完全一樣的,Hbase使用Rowkey來唯一的區分某一行的資料。

由於Hbase只支援3中查詢方式:

基於Rowkey的單行查詢

基於Rowkey的範圍掃描

全表掃描

因此,Rowkey對Hbase的效能影響非常大,Rowkey的設計就顯得尤為的重要。設計的時候要兼顧基於Rowkey的單行查詢也要鍵入Rowkey的範圍掃描。具體Rowkey要如何設計後續會整理相關的文章做進一步的描述。這裡大家只要有一個概念就是Rowkey的設計極為重要。

2.3、Region的概念

Region的概念和關係型資料庫的分割槽或者分片差不多。

Hbase會將一個大表的資料基於Rowkey的不同範圍分配到不通的Region中,每個Region負責一定範圍的資料訪問和儲存。這樣即使是一張巨大的表,由於被切割到不通的region,訪問起來的時延也很低。

2.4、TimeStamp的概念

TimeStamp對Hbase來說至關重要,因為它是實現Hbase多版本的關鍵。在Hbase中使用不同的timestame來標識相同rowkey行對應的不通版本的資料。

在寫入資料的時候,如果使用者沒有指定對應的timestamp,Hbase會自動新增一個timestamp,timestamp和伺服器時間保持一致。

在Hbase中,相同rowkey的資料按照timestamp倒序排列。預設查詢的是最新的版本,使用者可同指定timestamp的值來讀取舊版本的資料。

4、Hbase的架構

Hbase的架構圖如下圖所示:

從圖中可以看出Hbase是由Client、Zookeeper、Master、HRegionServer、HDFS等幾個組建組成,下面來介紹一下幾個組建的相關功能:

4.1、Client

Client包含了訪問Hbase的介面,另外Client還維護了對應的cache來加速Hbase的訪問,比如cache的.META.元資料的資訊。

4.2、Zookeeper

Hbase通過Zookeeper來做master的高可用、RegionServer的監控、元資料的入口以及叢集配置的維護等工作。具體工作如下:

通過Zoopkeeper來保證叢集中只有1個master在執行,如果master異常,會通過競爭機制產生新的master提供服務

通過Zoopkeeper來監控RegionServer的狀態,當RegionSevrer有異常的時候,通過回撥的形式通知Master RegionServer上下限的資訊

通過Zoopkeeper儲存元資料的統一入口地址

4.3、Hmaster

master節點的主要職責如下:

為RegionServer分配Region

維護整個叢集的負載均衡

維護叢集的元資料資訊

發現失效的Region,並將失效的Region分配到正常的RegionServer上

當RegionSever失效的時候,協調對應Hlog的拆分

4.4、HregionServer

HregionServer直接對接使用者的讀寫請求,是真正的“幹活”的節點。它的功能概括如下:

管理master為其分配的Region

處理來自客戶端的讀寫請求

負責和底層HDFS的互動,儲存資料到HDFS

負責Region變大以後的拆分

負責Storefile的合併工作

4.5、HDFS

HDFS為Hbase提供最終的底層資料儲存服務,同時為Hbase提供高可用(Hlog儲存在HDFS)的支援,具體功能概括如下:

提供元資料和表資料的底層分散式儲存服務

資料多副本,保證的高可靠和高可用性

5、Hbase的使用場景

Hbase是一個通過廉價PC機器叢集來儲存海量資料的分散式資料庫解決方案。它比較適合的場景概括如下:

是巨量大(百T、PB級別)

查詢簡單(基於rowkey或者rowkey範圍查詢)

不涉及到複雜的關聯

有幾個典型的場景特別適合使用Hbase來儲存:

海量訂單流水資料(長久儲存)

交易記錄

資料庫歷史資料

二、Hbase的Region介紹

前面已經介紹了Region類似於資料庫的分片和分割槽的概念,每個Region負責一小部分Rowkey範圍的資料的讀寫和維護,Region包含了對應的起始行到結束行的所有資訊。master將對應的region分配給不同的RergionServer,由RegionSever來提供Region的讀寫服務和相關的管理工作。這部分主要介紹Region例項以及Rgeion的尋找路徑:

1、region例項

上圖模擬了一個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的拆分那部分再具體的介紹。

2、Region的定址

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

2.1、老的Region定址方式

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

第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個數為2^34個,採用2層設計可以支援2^17(131072)。那麼2層設計的情況下一個叢集可以儲存4P的資料。這僅僅是一個Region只有128M的情況下。如果是10G呢? 因此,通過計算,其實2層設計就可以滿足叢集的需求。因此在0.96版本以後就去掉了-ROOT-表了。

2.2、新的Region定址方式

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

訪問路徑變成了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的最新地址。

三、Hbase的寫邏輯

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

1、Hbase寫入邏輯

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

從上圖可以看出氛圍3步驟:

第1步:Client獲取資料寫入的Region所在的RegionServer

第2步:請求寫Hlog

第3步:請求寫MemStore

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

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

2、MemStore刷盤

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

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、MemStore達到上限

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

2.3、RegionServer的Hlog數量達到上限

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

2.4、手工觸發

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

2.5、關閉RegionServer觸發

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

2.6、Region使用HLOG恢復完資料後觸發

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

3、Hlog

3.1、Hlog簡介

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

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

3.2、Hlog結構

從上圖我們可以看出都個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的就直接忽略,但與或者等於的就進行重做。回放完成後,就完成了資料的恢復工作。

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目錄保留的最長時間。

四、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中模式,下面就一一來介紹。

1、LogSplitting

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

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架構。

2、Distributed Log Splitting

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

上圖的操作順序如下:

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,那麼產生的小檔案數量為20*50=1000個。如果叢集中有多臺RegionServer宕機的情況,小檔案更是會成倍增加,恢復的過程還是會比較慢。由次誕生了Distributed Log Replay模式。

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消耗。如下圖所示:

五、Region的拆分

1、Hbase Region的三種拆分策略

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

1.1、ConstantSizeRegionSplitPolicy

ConstantSizeRegionSplitPolicy策略是0.94版本之前的預設拆分策略,這個策略的拆分規則是:當region大小達到hbase.hregion.max.filesize(預設10G)後拆分。

這種拆分策略對於小表不太友好,按照預設的設定,如果1個表的Hfile小於10G就一直不會拆分。注意10G是壓縮後的大小,如果使用了壓縮的話。

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

1.2、IncreasingToUpperBoundRegionSplitPolicy

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

從這個算是我們可以得出flushsize為128M、maxFileSize為10G的情況下,可以計算出Region的分裂情況如下:

第一次拆分大小為:min(10G,1*1*128M)=128M

第二次拆分大小為:min(10G,3*3*128M)=1152M

第三次拆分大小為:min(10G,5*5*128M)=3200M

第四次拆分大小為:min(10G,7*7*128M)=6272M

第五次拆分大小為:min(10G,9*9*128M)=10G

第五次拆分大小為:min(10G,11*11*128M)=10G

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

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策略,對於小表也肯呢個比較好的適配。

2、Hbase Region拆分的詳細流程

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

從上圖我們可以看出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檔案如何才能查詢到對應的資料呢?如下圖所示:

根據檔名來判斷是否是reference檔案

由於reference檔案的命名規則為前半部分為父Region對應的File的檔名,後半部分是父Region的名稱,因此讀取的時候也根據前半部分和後半部分來識別

根據reference檔案的內容來確定掃描的範圍,reference的內容包含兩部分,一部分是切分點splitkey,另一部分是boolean型別的變數(true或者false)。如果為true則掃描檔案的上半部分,false則掃描檔案的下半部分

接下來確定了掃描的檔案,以及檔案的掃描範圍,那就按照正常的檔案檢索了

六、Region的合併

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

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消耗以及頻寬消耗換取後續查詢的低延遲。

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。建議禁用。

七、參考資料

參考了很多前人的資料,尤其是http://hbasefly.com,部落格寫得很棒,羅列如下:

http://hbase.apache.org/

https://zh.hortonworks.com/blog/apache-hbase-region-splitting-and-merging/

http://blog.csdn.net/u011490320/article/details/50814967

http://hbasefly.com/2016/12/21/hbase-getorscan/

http://blog.csdn.net/xgjianstart/article/details/53290155

http://blog.csdn.net/weihongrao/article/details/17281991

http://www.aboutyun.com/thread-20207-1-1.html

http://hbasefly.com/2017/08/27/hbase-split/

http://blog.csdn.net/weihongrao/article/details/17281991

http://blog.csdn.net/gaijianwei/article/details/46271011

http://blog.csdn.net/u010270403/article/details/51648462