一、Hbase介紹
1、Hbase簡介
Hbase是Hadoop Database的簡稱 ,Hbase項目是由Powerset公司的Chad Walters和Jim Kelleman在2006年末發(fā)起,根據(jù)Google的Chang等人發(fā)表的論文“Bigtable:A Distributed Storage System for Strctured Data“來設計的。2007年10月發(fā)布了第一個版本。2010年5月,Hbase從Hadoop子項目升級成Apache頂級項目。
Hbase是分布式、面向列的開源數(shù)據(jù)庫(其實準確的說是面向列族)。HDFS為Hbase提供可靠的底層數(shù)據(jù)存儲服務,MapReduce為Hbase提供高性能的計算能力,Zookeeper為Hbase提供穩(wěn)定服務和Failover機制,因此我們說Hbase是一個通過大量廉價的機器解決海量數(shù)據(jù)的高速存儲和讀取的分布式數(shù)據(jù)庫解決方案。
2、Hbase幾個特點介紹
提煉出Hbase的幾個特點,如下圖所示:

2.1、海量存儲
Hbase適合存儲PB級別的海量數(shù)據(jù),在PB級別的數(shù)據(jù)以及采用廉價PC存儲的情況下,能在幾十到百毫秒內(nèi)返回數(shù)據(jù)。這與Hbase的極易擴展性息息相關(guān)。正式因為Hbase良好的擴展性,才為海量數(shù)據(jù)的存儲提供了便利。
2.2、列式存儲
這里的列式存儲其實說的是列族存儲,Hbase是根據(jù)列族來存儲數(shù)據(jù)的。列族下面可以有非常多的列,列族在創(chuàng)建表的時候就必須指定。為了加深對Hbase列族的理解,下面是一個簡單的關(guān)系型數(shù)據(jù)庫的表和Hbase數(shù)據(jù)庫的表:
RDBMS的表:

Hbase的表:

下圖是針對Hbase和關(guān)系型數(shù)據(jù)庫的基本的一個比較:

2.3、極易擴展
Hbase的擴展性主要體現(xiàn)在兩個方面,一個是基于上層處理能力(RegionServer)的擴展,一個是基于存儲的擴展(HDFS)。
通過橫向添加RegionSever的機器,進行水平擴展,提升Hbase上層的處理能力,提升Hbsae服務更多Region的能力。
備注:RegionServer的作用是管理region、承接業(yè)務的訪問,這個后面會詳細的介紹
通過橫向添加Datanode的機器,進行存儲層擴容,提升Hbase的數(shù)據(jù)存儲能力和提升后端存儲的讀寫能力。
2.4、高并發(fā)
由于目前大部分使用Hbase的架構(gòu),都是采用的廉價PC,因此單個IO的延遲其實并不小,一般在幾十到上百ms之間。這里說的高并發(fā),主要是在并發(fā)的情況下,Hbase的單個IO延遲下降并不多。能獲得高并發(fā)、低延遲的服務。
2.5、稀疏
稀疏主要是針對Hbase列的靈活性,在列族中,你可以指定任意多的列,在列數(shù)據(jù)為空的情況下,是不會占用存儲空間的。
3、Hbase的幾個概念介紹
在我學習Hbase的時候有幾個概念需要重點理解一下,列出4個基礎概念如下圖所示:

2.1、Column Family的概念
Column Family又叫列族,Hbase通過列族劃分數(shù)據(jù)的存儲,列族下面可以包含任意多的列,實現(xiàn)靈活的數(shù)據(jù)存取。剛接觸的時候,理解起來有點吃力。我想到了一個非常類似的概念,理解起來就非常容易了。那就是家族的概念,我們知道一個家族是由于很多個的家庭組成的。列族也類似,列族是由一個一個的列組成(任意多)。
Hbase表的創(chuàng)建的時候就必須指定列族。就像關(guān)系型數(shù)據(jù)庫創(chuàng)建的時候必須指定具體的列是一樣的。
Hbase的列族不是越多越好,官方推薦的是列族最好小于或者等于3。我們使用的場景一般是1個列族。
2.2、Rowkey的概念
Rowkey的概念和mysql中的主鍵是完全一樣的,Hbase使用Rowkey來唯一的區(qū)分某一行的數(shù)據(jù)。
由于Hbase只支持3中查詢方式:
基于Rowkey的單行查詢
基于Rowkey的范圍掃描
全表掃描
因此,Rowkey對Hbase的性能影響非常大,Rowkey的設計就顯得尤為的重要。設計的時候要兼顧基于Rowkey的單行查詢也要鍵入Rowkey的范圍掃描。具體Rowkey要如何設計后續(xù)會整理相關(guān)的文章做進一步的描述。這里大家只要有一個概念就是Rowkey的設計極為重要。
2.3、Region的概念
Region的概念和關(guān)系型數(shù)據(jù)庫的分區(qū)或者分片差不多。
Hbase會將一個大表的數(shù)據(jù)基于Rowkey的不同范圍分配到不通的Region中,每個Region負責一定范圍的數(shù)據(jù)訪問和存儲。這樣即使是一張巨大的表,由于被切割到不通的region,訪問起來的時延也很低。
2.4、TimeStamp的概念
TimeStamp對Hbase來說至關(guān)重要,因為它是實現(xiàn)Hbase多版本的關(guān)鍵。在Hbase中使用不同的timestame來標識相同rowkey行對應的不通版本的數(shù)據(jù)。
在寫入數(shù)據(jù)的時候,如果用戶沒有指定對應的timestamp,Hbase會自動添加一個timestamp,timestamp和服務器時間保持一致。
在Hbase中,相同rowkey的數(shù)據(jù)按照timestamp倒序排列。默認查詢的是最新的版本,用戶可同指定timestamp的值來讀取舊版本的數(shù)據(jù)。
4、Hbase的架構(gòu)
Hbase的架構(gòu)圖如下圖所示:

從圖中可以看出Hbase是由Client、Zookeeper、Master、HRegionServer、HDFS等幾個組建組成,下面來介紹一下幾個組建的相關(guān)功能:
4.1、Client
Client包含了訪問Hbase的接口,另外Client還維護了對應的cache來加速Hbase的訪問,比如cache的.META.元數(shù)據(jù)的信息。
4.2、Zookeeper
Hbase通過Zookeeper來做master的高可用、RegionServer的監(jiān)控、元數(shù)據(jù)的入口以及集群配置的維護等工作。具體工作如下:
通過Zoopkeeper來保證集群中只有1個master在運行,如果master異常,會通過競爭機制產(chǎn)生新的master提供服務
通過Zoopkeeper來監(jiān)控RegionServer的狀態(tài),當RegionSevrer有異常的時候,通過回調(diào)的形式通知Master RegionServer上下限的信息
通過Zoopkeeper存儲元數(shù)據(jù)的統(tǒng)一入口地址
4.3、Hmaster
master節(jié)點的主要職責如下:
為RegionServer分配Region
維護整個集群的負載均衡
維護集群的元數(shù)據(jù)信息
發(fā)現(xiàn)失效的Region,并將失效的Region分配到正常的RegionServer上
當RegionSever失效的時候,協(xié)調(diào)對應Hlog的拆分
4.4、HregionServer
HregionServer直接對接用戶的讀寫請求,是真正的“干活”的節(jié)點。它的功能概括如下:
管理master為其分配的Region
處理來自客戶端的讀寫請求
負責和底層HDFS的交互,存儲數(shù)據(jù)到HDFS
負責Region變大以后的拆分
負責Storefile的合并工作
4.5、HDFS
HDFS為Hbase提供最終的底層數(shù)據(jù)存儲服務,同時為Hbase提供高可用(Hlog存儲在HDFS)的支持,具體功能概括如下:
提供元數(shù)據(jù)和表數(shù)據(jù)的底層分布式存儲服務
數(shù)據(jù)多副本,保證的高可靠和高可用性
二、Hbase的Region介紹
前面已經(jīng)介紹了Region類似于數(shù)據(jù)庫的分片和分區(qū)的概念,每個Region負責一小部分Rowkey范圍的數(shù)據(jù)的讀寫和維護,Region包含了對應的起始行到結(jié)束行的所有信息。master將對應的region分配給不同的RergionServer,由RegionSever來提供Region的讀寫服務和相關(guān)的管理工作。這部分主要介紹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)
備注:這里只是為了更容易的說明拆分的規(guī)則,其實真實的場景并不會幾條記錄拆分到不通的Region上,而是到一定的數(shù)據(jù)量才會拆分,具體的在Region的拆分那部分再具體的介紹。
2、Region的尋址
既然讀寫都在RegionServer上發(fā)生,我們前面有講到,每個RegionSever為一定數(shù)量的region服務,那么client要對某一行數(shù)據(jù)做讀寫的時候如何能知道具體要去訪問哪個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-的相關(guān)信息cache下來,以便下一次快速訪問
第3步:client請求.META.表的RS地址,獲取訪問數(shù)據(jù)所在RegionServer的地址,client會將.META.的相關(guān)信息cache下來,以便下一次快速訪問
第4步:client請求訪問數(shù)據(jù)所在RegionServer的地址,獲取對應的數(shù)據(jù)
從上面的路徑我們可以看出,用戶需要3次請求才能直到用戶Table真正的位置,這在一定程序帶來了性能的下降。在0.96之前使用3層設計的主要原因是考慮到元數(shù)據(jù)可能需要很大。但是真正集群運行,元數(shù)據(jù)的大小其實很容易計算出來。在BigTable的論文中,每行METADATA數(shù)據(jù)存儲大小為1KB左右,如果按照一個Region為128M的計算,3層設計可以支持的Region個數(shù)為2^34個,采用2層設計可以支持2^17(131072)。那么2層設計的情況下一個集群可以存儲4P的數(shù)據(jù)。這僅僅是一個Region只有128M的情況下。如果是10G呢? 因此,通過計算,其實2層設計就可以滿足集群的需求。因此在0.96版本以后就去掉了-ROOT-表了。
2.2、新的Region尋址方式
如上面的計算,2層結(jié)構(gòu)其實完全能滿足業(yè)務的需求,因此0.96版本以后將-ROOT-表去掉了。如下圖所示:

訪問路徑變成了3步:
第1步:Client請求ZK獲取.META.所在的RegionServer的地址。
第2步:Client請求.META.所在的RegionServer獲取訪問數(shù)據(jù)所在的RegionServer地址,client會將.META.的相關(guān)信息cache下來,以便下一次快速訪問。
第3步:Client請求數(shù)據(jù)所在的RegionServer,獲取所需要的數(shù)據(jù)。
總結(jié)去掉-ROOT-的原因有如下2點:
其一:提高性能
其二:2層結(jié)構(gòu)已經(jīng)足以滿足集群的需求
這里還有一個問題需要說明,那就是Client會緩存.META.的數(shù)據(jù),用來加快訪問,既然有緩存,那它什么時候更新?如果.META.更新了,比如Region1不在RerverServer2上了,被轉(zhuǎn)移到了RerverServer3上。client的緩存沒有更新會有什么情況?
其實,Client的元數(shù)據(jù)緩存不更新,當.META.的數(shù)據(jù)發(fā)生更新。如上面的例子,由于Region1的位置發(fā)生了變化,Client再次根據(jù)緩存去訪問的時候,會出現(xiàn)錯誤,當出現(xiàn)異常達到重試次數(shù)后就會去.META.所在的RegionServer獲取最新的數(shù)據(jù),如果.META.所在的RegionServer也變了,Client就會去ZK上獲取.META.所在的RegionServer的最新地址。
三、Hbase的寫邏輯
Hbase的寫邏輯涉及到寫內(nèi)存、寫log、刷盤等操作,看起來簡單,其實里面又有很多的邏輯,下面就來做詳細的介紹
1、Hbase寫入邏輯
Hbase的寫入流程如下圖所示:

從上圖可以看出氛圍3步驟:
第1步:Client獲取數(shù)據(jù)寫入的Region所在的RegionServer
第2步:請求寫Hlog
第3步:請求寫MemStore
只有當寫Hlog和寫MemStore都成功了才算請求寫入完成。MemStore后續(xù)會逐漸刷到HDFS中。
備注:Hlog存儲在HDFS,當RegionServer出現(xiàn)異常,需要使用Hlog來恢復數(shù)據(jù)。
2、MemStore刷盤
為了提高Hbase的寫入性能,當寫請求寫入MemStore后,不會立即刷盤。而是會等到一定的時候進行刷盤的操作。具體是哪些場景會觸發(fā)刷盤的操作呢?總結(jié)成如下的幾個場景:
2.1、全局內(nèi)存控制
這個全局的參數(shù)是控制內(nèi)存整體的使用情況,當所有memstore占整個heap的最大比例的時候,會觸發(fā)刷盤的操作。這個參數(shù)是hbase.regionserver.global.memstore.upperLimit,默認為整個heap內(nèi)存的40%。但這并不意味著全局內(nèi)存觸發(fā)的刷盤操作會將所有的MemStore都進行輸盤,而是通過另外一個參數(shù)hbase.regionserver.global.memstore.lowerLimit來控制,默認是整個heap內(nèi)存的35%。當flush到所有memstore占整個heap內(nèi)存的比率為35%的時候,就停止刷盤。這么做主要是為了減少刷盤對業(yè)務帶來的影響,實現(xiàn)平滑系統(tǒng)負載的目的。
2.2、MemStore達到上限
當MemStore的大小達到hbase.hregion.memstore.flush.size大小的時候會觸發(fā)刷盤,默認128M大小
2.3、RegionServer的Hlog數(shù)量達到上限
前面說到Hlog為了保證Hbase數(shù)據(jù)的一致性,那么如果Hlog太多的話,會導致故障恢復的時間太長,因此Hbase會對Hlog的最大個數(shù)做限制。當達到Hlog的最大個數(shù)的時候,會強制刷盤。這個參數(shù)是hase.regionserver.max.logs,默認是32個。
2.4、手工觸發(fā)
可以通過hbase shell或者java api手工觸發(fā)flush的操作。
2.5、關(guān)閉RegionServer觸發(fā)
在正常關(guān)閉RegionServer會觸發(fā)刷盤的操作,全部數(shù)據(jù)刷盤后就不需要再使用Hlog恢復數(shù)據(jù)。
2.6、Region使用HLOG恢復完數(shù)據(jù)后觸發(fā)
當RegionServer出現(xiàn)故障的時候,其上面的Region會遷移到其他正常的RegionServer上,在恢復完Region的數(shù)據(jù)后,會觸發(fā)刷盤,當刷盤完成后才會提供給業(yè)務訪問。
3、Hlog
3.1、Hlog簡介
Hlog是Hbase實現(xiàn)WAL(Write ahead log)方式產(chǎn)生的日志信息,內(nèi)部是一個簡單的順序日志。每個RegionServer對應1個Hlog(備注:1.x版本的可以開啟MultiWAL功能,允許多個Hlog),所有對于該RegionServer的寫入都被記錄到Hlog中。Hlog實現(xiàn)的功能就是我們前面講到的保證數(shù)據(jù)安全。當RegionServer出現(xiàn)問題的時候,能跟進Hlog來做數(shù)據(jù)恢復。此外為了保證恢復的效率,Hbase會限制最大保存的Hlog數(shù)量,如果達到Hlog的最大個數(shù)(hase.regionserver.max.logs參數(shù)控制)的時候,就會觸發(fā)強制刷盤操作。對于已經(jīng)刷盤的數(shù)據(jù),其對應的Hlog會有一個過期的概念,Hlog過期后,會被監(jiān)控線程移動到.oldlogs,然后會被自動刪除掉。
Hbase是如何判斷Hlog過期的呢?要找到這個答案,我們就必須了解Hlog的詳細結(jié)構(gòu)。
3.2、Hlog結(jié)構(gòu)
下圖是Hlog的詳細結(jié)構(gòu)(圖片來源http://hbasefly.com/):

從上圖我們可以看出都個Region共享一個Hlog文件,單個Region在Hlog中是按照時間順序存儲的,但是多個Region可能并不是完全按照時間順序。
每個Hlog最小單元由Hlogkey和WALEdit兩部分組成。Hlogky由sequenceid、timestamp、cluster ids、regionname以及tablename等組成,WALEdit是由一系列的KeyValue組成,對一行上所有列(即所有KeyValue)的更新操作,都包含在同一個WALEdit對象中,這主要是為了實現(xiàn)寫入一行多個列時的原子性。
注意,圖中有個sequenceid的東東。sequenceid是一個store級別的自增序列號,這東東非常重要,region的數(shù)據(jù)恢復和Hlog過期清除都要依賴這個東東。下面就來簡單描述一下sequenceid的相關(guān)邏輯。
Memstore在達到一定的條件會觸發(fā)刷盤的操作,刷盤的時候會獲取刷新到最新的一個sequenceid的下一個sequenceid,并將新的sequenceid賦給oldestUnflushedSequenceId,并刷到Ffile中。有點繞,舉個例子來說明:比如對于某一個store,開始的時候oldestUnflushedSequenceId為NULL,此時,如果觸發(fā)flush的操作,假設初始刷盤到sequenceid為10,那么hbase會在10的基礎上append一個空的Entry到HLog,最新的sequenceid為11,然后將sequenceid為11的號賦給oldestUnflushedSequenceId,并將oldestUnflushedSequenceId的值刷到Hfile文件中進行持久化。
Hlog文件對應所有Region的store中最大的sequenceid如果已經(jīng)刷盤,就認為Hlog文件已經(jīng)過期,就會移動到.oldlogs,等待被移除。
當RegionServer出現(xiàn)故障的時候,需要對Hlog進行回放來恢復數(shù)據(jù)。回放的時候會讀取Hfile的oldestUnflushedSequenceId中的sequenceid和Hlog中的sequenceid進行比較,小于sequenceid的就直接忽略,但與或者等于的就進行重做?;胤磐瓿珊?,就完成了數(shù)據(jù)的恢復工作。
3.3、Hlog的生命周期
Hlog從產(chǎn)生到最后刪除需要經(jīng)歷如下幾個過程:
產(chǎn)生
所有涉及到數(shù)據(jù)的變更都會先寫Hlog,除非是你關(guān)閉了Hlog
滾動
Hlog的大小通過參數(shù)hbase.regionserver.logroll.period控制,默認是1個小時,時間達到hbase.regionserver.logroll.period 設置的時間,Hbase會創(chuàng)建一個新的Hlog文件。這就實現(xiàn)了Hlog滾動的目的。Hbase通過hbase.regionserver.maxlogs參數(shù)控制Hlog的個數(shù)。滾動的目的,為了控制單個Hlog文件過大的情況,方便后續(xù)的過期和刪除。
過期
前面我們有講到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執(zhí)行完一個Hlog的時候,會刪除Zoopkeeper上的對應Hlog節(jié)點。在Hlog被移動到.oldlogs目錄后,Hbase每隔hbase.master.cleaner.interval(默認60秒)時間會去檢查.oldlogs目錄下的所有Hlog,確認對應的Zookeeper的Hlog節(jié)點是否被刪除,如果Zookeeper 上不存在對應的Hlog節(jié)點,那么就直接刪除對應的Hlog。
hbase.master.logcleaner.ttl(默認10分鐘)這個參數(shù)設置Hlog在.oldlogs目錄保留的最長時間。
四、RegionServer的故障恢復
我們知道,RegionServer的相關(guān)信息保存在ZK中,在RegionServer啟動的時候,會在Zookeeper中創(chuàng)建對應的臨時節(jié)點。RegionServer通過Socket和Zookeeper建立session會話,RegionServer會周期性地向Zookeeper發(fā)送ping消息包,以此說明自己還處于存活狀態(tài)。而Zookeeper收到ping包后,則會更新對應session的超時時間。
當Zookeeper超過session超時時間還未收到RegionServer的ping包,則Zookeeper會認為該RegionServer出現(xiàn)故障,ZK會將該RegionServer對應的臨時節(jié)點刪除,并通知Master,Master收到RegionServer掛掉的信息后就會啟動數(shù)據(jù)恢復的流程。
Master啟動數(shù)據(jù)恢復流程后,其實主要的流程如下:
RegionServer宕機---》ZK檢測到RegionServer異常---》Master啟動數(shù)據(jù)恢復---》Hlog切分---》Region重新分配---》Hlog重放---》恢復完成并提供服務
故障恢復有3中模式,下面就一一來介紹。
1、LogSplitting
在最開始的恢復流程中,Hlog的整個切分過程都由于Master來執(zhí)行,如下圖所示:

a、將待切分的日志文件夾進行重命名,防止RegionServer未真的宕機而持續(xù)寫入Hlog
b、Master啟動讀取線程讀取Hlog的數(shù)據(jù),并將不同RegionServer的日志寫入到不通的內(nèi)存buffer中
c、針對每個buffer,Master會啟動對應的寫線程將不同Region的buffer數(shù)據(jù)寫入到HDFS中,對應的路徑為/hbase/table_name/region/recoverd.edits/.tmp。
d、Master重新將宕機的RegionServer中的Rgion分配到正常的RegionServer中,對應的RegionServer讀取Region的數(shù)據(jù),會發(fā)現(xiàn)該region目錄下的recoverd.edits目錄以及相關(guān)的日志,然后RegionServer重放對應的Hlog日志,從而實現(xiàn)對應Region數(shù)據(jù)的恢復。
從上面的步驟中,我們可以看出Hlog的切分一直都是master在干活,效率比較低。設想,如果集群中有多臺RegionServer在同一時間宕機,會是什么情況?串行修復,肯定異常慢,因為只有master一個人在干Hlog切分的活。因此,為了提高效率,開發(fā)了Distributed Log Splitting架構(gòu)。
2、Distributed Log Splitting
顧名思義,Distributed Log Splitting是LogSplitting的分布式實現(xiàn),分布式就不是master一個人在干活了,而是充分使用各個RegionServer上的資源,利用多個RegionServer來并行切分Hlog,提高切分的效率。如下圖所示:

上圖的操作順序如下:
a、Master將要切分的日志發(fā)布到Zookeeper節(jié)點上(/hbase/splitWAL),每個Hlog日志一個任務,任務的初始狀態(tài)為TASK_UNASSIGNED
b、在Master發(fā)布Hlog任務后,RegionServer會采用競爭方式認領對應的任務(先查看任務的狀態(tài),如果是TASK_UNASSIGNED,就將該任務狀態(tài)修改為TASK_OWNED)
c、RegionServer取得任務后會讓對應的HLogSplitter線程處理Hlog的切分,切分的時候讀取出Hlog的對,然后寫入不通的Region buffer的內(nèi)存中。
d、RegionServer啟動對應寫線程,將Region buffer的數(shù)據(jù)寫入到HDFS中,路徑為/hbase/table/region/seqenceid.temp,seqenceid是一個日志中該Region對應的最大sequenceid,如果日志切分成功,而RegionServer會將對應的ZK節(jié)點的任務修改為TASK_DONE,如果切分失敗,則會將任務修改為TASK_ERR。
e、如果任務是TASK_ERR狀態(tài),則Master會重新發(fā)布該任務,繼續(xù)由RegionServer競爭任務,并做切分處理。
f、Master重新將宕機的RegionServer中的Rgion分配到正常的RegionServer中,對應的RegionServer讀取Region的數(shù)據(jù),將該region目錄下的一系列的seqenceid.temp進行從小到大進行重放,從而實現(xiàn)對應Region數(shù)據(jù)的恢復。
從上面的步驟中,我們可以看出Distributed Log Splitting采用分布式的方式,使用多臺RegionServer做Hlog的切分工作,確實能提高效率。正常故障恢復可以降低到分鐘級別。但是這種方式有個弊端是會產(chǎn)生很多小文件(切分的Hlog數(shù) * 宕機的RegionServer上的Region數(shù))。比如一個RegionServer有20個Region,有50個Hlog,那么產(chǎn)生的小文件數(shù)量為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版本之前的默認拆分策略,這個策略的拆分規(guī)則是:當region大小達到hbase.hregion.max.filesize(默認10G)后拆分。
這種拆分策略對于小表不太友好,按照默認的設置,如果1個表的Hfile小于10G就一直不會拆分。注意10G是壓縮后的大小,如果使用了壓縮的話。
如果1個表一直不拆分,訪問量小也不會有問題,但是如果這個表訪問量比較大的話,就比較容易出現(xiàn)性能問題。這個時候只能手工進行拆分。還是很不方便。
1.2、IncreasingToUpperBoundRegionSplitPolicy
IncreasingToUpperBoundRegionSplitPolicy策略是Hbase的0.94~2.0版本默認的拆分策略,這個策略相較于ConstantSizeRegionSplitPolicy策略做了一些優(yōu)化,該策略的算法為: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
從上面的計算我們可以看到這種策略能夠自適應大表和小表,但是這種策略會導致小表產(chǎn)生比較多的小region,對于小表還是不是很完美。
1.3、SteppingSplitPolicy
SteppingSplitPolicy是在Hbase 2.0版本后的默認策略,,拆分規(guī)則為:If region=1 then: flush size * 2 else: MaxRegionFileSize。
還是以flushsize為128M、maxFileSize為10場景為列,計算出Region的分裂情況如下:
第一次拆分大小為:2*128M=256M
第二次拆分大小為:10G
從上面的計算我們可以看出,這種策略兼顧了ConstantSizeRegionSplitPolicy策略和IncreasingToUpperBoundRegionSplitPolicy策略,對于小表也肯呢個比較好的適配。
2、Hbase Region拆分的詳細流程
Hbase的詳細拆分流程圖如下:

備注圖片來源(https://zh.hortonworks.com/blog/apache-hbase-region-splitting-and-merging/)
從上圖我們可以看出Region切分的詳細流程如下:
第1步會ZK的/hbase/region-in-transition/region-name下創(chuàng)建一個znode,并設置狀態(tài)為SPLITTING
第2步master通過watch節(jié)點檢測到Region狀態(tài)的變化,并修改內(nèi)存中Region狀態(tài)的變化
第3步RegionServer在父Region的目錄下創(chuàng)建一個名稱為.splits的子目錄
第4步RegionServer關(guān)閉父Region,強制將數(shù)據(jù)刷新到磁盤,并這個Region標記為offline的狀態(tài)。此時,落到這個Region的請求都會返回NotServingRegionException這個錯誤
第5步RegionServer在.splits創(chuàng)建daughterA和daughterB,并在文件夾中創(chuàng)建對應的reference文件,指向父Region的Region文件
第6步RegionServer在HDFS中創(chuàng)建daughterA和daughterB的Region目錄,并將reference文件移動到對應的Region目錄中
第7步在.META.表中設置父Region為offline狀態(tài),不再提供服務,并將父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的狀態(tài)為SPLIT
備注:為了減少對業(yè)務的影響,Region的拆分并不涉及到數(shù)據(jù)遷移的操作,而只是創(chuàng)建了對父Region的指向。只有在做大合并的時候,才會將數(shù)據(jù)進行遷移。
那么通過reference文件如何才能查找到對應的數(shù)據(jù)呢?如下圖所示:

根據(jù)文件名來判斷是否是reference文件
由于reference文件的命名規(guī)則為前半部分為父Region對應的File的文件名,后半部分是父Region的名稱,因此讀取的時候也根據(jù)前半部分和后半部分來識別
根據(jù)reference文件的內(nèi)容來確定掃描的范圍,reference的內(nèi)容包含兩部分,一部分是切分點splitkey,另一部分是boolean類型的變量(true或者false)。如果為true則掃描文件的上半部分,false則掃描文件的下半部分
接下來確定了掃描的文件,以及文件的掃描范圍,那就按照正常的文件檢索了
六、Region的合并
Region的合并分為小合并和大合并,下面就分別來做介紹:
1、小合并(MinorCompaction)
由前面的刷盤部分的介紹,我們知道當MemStore達到hbase.hregion.memstore.flush.size大小的時候會將數(shù)據(jù)刷到磁盤,生產(chǎn)StoreFile,因此勢必產(chǎn)生很多的小問題,對于Hbase的讀取,如果要掃描大量的小文件,會導致性能很差,因此需要將這些小文件合并成大一點的文件。因此所謂的小合并,就是把多個小的StoreFile組合在一起,形成一個較大的StoreFile,通常是累積到3個Store File后執(zhí)行。通過參數(shù)hbase.hstore,compactionThreadhold配置。小合并的大致步驟為:
分別讀取出待合并的StoreFile文件的KeyValues,并順序地寫入到位于./tmp目錄下的臨時文件中
將臨時文件移動到對應的Region目錄中
將合并的輸入文件路徑和輸出路徑封裝成KeyValues寫入WAL日志,并打上compaction標記,最后強制自行sync
將對應region數(shù)據(jù)目錄下的合并的輸入文件全部刪除,合并完成
這種小合并一般速度很快,對業(yè)務的影響也比較小。本質(zhì)上,小合并就是使用短時間的IO消耗以及帶寬消耗換取后續(xù)查詢的低延遲。
2、大合并(MajorCompaction)
所謂的大合并,就是將一個Region下的所有StoreFile合并成一個StoreFile文件,在大合并的過程中,之前刪除的行和過期的版本都會被刪除,拆分的母Region的數(shù)據(jù)也會遷移到拆分后的子Region上。大合并一般一周做一次,控制參數(shù)為hbase.hregion.majorcompaction。大合并的影響一般比較大,盡量避免統(tǒng)一時間多個Region進行合并,因此Hbase通過一些參數(shù)來進行控制,用于防止多個Region同時進行大合并。該參數(shù)為:hbase.hregion.majorcompaction.jitter
具體算法為:
hbase.hregion.majorcompaction參數(shù)的值乘于一個隨機分數(shù),這個隨機分數(shù)不能超過hbase.hregion.majorcompaction.jitter的值。hbase.hregion.majorcompaction.jitter的值默認為0.5。
通過hbase.hregion.majorcompaction參數(shù)的值加上或減去hbase.hregion.majorcompaction參數(shù)的值乘于一個隨機分數(shù)的值就確定下一次大合并的時間區(qū)間。
用戶如果想禁用major compaction,只需要將參數(shù)hbase.hregion.majorcompaction設為0。建議禁用