上一篇:超詳細梳理HBase核心知識點(上)–建議收藏
本篇主要介紹:
① Region的拆分和合并,及相關經驗總結,
② Region自動均衡
③ HFile的合并(compaction)
④ BlockCache 和 BloomFilter
一、Region拆分與合并
一個Region就是一個表 的一段Rowkey的資料集合,當Region太大的時候HBase會拆分它,這么做的原因是:當某個Region太大的時候讀取效率太低了,Region的拆分分為自動拆分和手動拆分,自動拆分可以采用不同的策略,
1.1 Region自動拆分
Hbase的Region自動拆分有幾種策略,這里簡單介紹一下概念,拆分策略的具體內容讀者可以下去自行深入研究,
- ConstantSizeRegionSplitPolicy
早在0.94版本的時候HBase只有一種拆分策略,這個策 略就是按照固定大小來拆分Region,它唯一用到的引數是:
hbase.hregion.max.filesize: region最大大小,默認10GB
當單個Region大小超過了10GB,就會被HBase拆分成為2個Region,
- IncreasingToUpperBoundRegionSplitPolicy(默認)
0.94版本之后,有了IncreasingToUpperBoundRegionSplitPolicy 策略,這種策略從名字上就可以看出是限制不斷增長的檔案尺寸的策略用來防止Region過大,
- KeyPrefixRegionSplitPolicy
除了簡單粗暴地根據大小來拆分,我們還可以自己定義拆分點,
KeyPrefixRegionSplitPolicy 是 IncreasingToUpperBoundRegionSplitPolicy的子類,在前者的基礎上 增加了對拆分點(splitPoint,拆分點就是Region被拆分處的rowkey) 的定義,它保證了有相同前綴的rowkey不會被拆分到兩個不同的Region 里面,
- DelimitedKeyPrefixRegionSplitPolicy
該策略也是繼承自IncreasingToUpperBoundRegionSplitPolicy, 它也是根據你的rowkey前綴來進行切分的,唯一的不同就是: KeyPrefixRegionSplitPolicy是根據rowkey的固定前幾位字符來進行判 斷,而DelimitedKeyPrefixRegionSplitPolicy是根據分隔符來判斷的,比如你定義了前綴分隔符為_,那么host1_001和host12_999的前綴 就分別是host1和host12,
- BusyRegionSplitPolicy
此前的拆分策略都沒有考慮熱點問題,所謂熱點問題就是資料庫中 的Region被訪問的頻率并不一樣,某些Region在短時間內被訪問的很頻 繁,承載了很大的壓力,這些Region就是熱點Region,如果你的系統常常會出現熱點Region,而你對性能有很高的追求, 那么這種策略可能會比較適合你,它會通過拆分熱點Region來緩解熱點 Region的壓力,但是根據熱點來拆分Region也會帶來很多不確定性因 素,因為你也不知道下一個被拆分的Region是哪個,
- DisabledRegionSplitPolicy
這種策略其實不是一種策略,如果你看這個策略的原始碼會發現就一 個方法shouldSplit,并且永遠回傳false,設定成這種策略就是Region永不自動拆分, 如果使用DisabledRegionSplitPolicy讓Region永不自動拆分之 后,你依然可以通過手動拆分來拆分Region,
1.2 Region手動拆分
無論你設定了哪種拆分策略,一開始資料進入Hbase的時候都只會 往一個Region塞資料,必須要等到一個Region的大小膨脹到某個閥值的 時候才會根據拆分策略來進行拆分,但是當大量的資料涌入的時候,可 能會出現一邊拆分一邊寫入大量資料的情況,由于拆分要占用大量IO, 有可能對資料庫造成一定的壓力,如果你事先就知道這個Table應該按 怎樣的策略來拆分Region的話,你也可以事先定義拆分點 (SplitPoint),所謂拆分點就是拆分處的rowkey,比如你可以按26個 字母來定義25個拆分點,這樣資料一到HBase就會被分配到各自所屬的 Region里面,這時候我們就可以把自動拆分關掉,只用手動拆分, 手動拆分有兩種情況:預拆分(pre-splitting)和強制拆分 (forced splits),
1.2.1 Region預拆分
預拆分(pre-splitting)就是在建表的時候就定義好了拆分點的演算法,所以叫預拆分,
默認的拆分點演算法有兩個:UniformSplit和HexStringSplit
假設要拆分的Region的數量是n,HexStringSplit把資料從“00000000”到“FFFFFFFF”之間的資料 長度按照n等分之后算出每一段的起始rowkey和結束rowkey,以此作為 拆分點,
UniformSplit有點像HexStringSplit的byte版,不管傳參還是n, 唯一不一樣的是起始和結束不是String,而是byte[],
舉個例子:
hbase org.apache.hadoop.hbase.util.RegionSplitter my_split_table HexStringSplit -c 10 -f mycf
執行如上命令的結果:新建了一張表,并且規定了該表的Region數量永遠只有10個,
-c:要拆分的Region數量,
-f:要建立的列族名稱,
還可以手動指定拆分點,在建表的時候加上SPLITS引數
create 'test_split2', 'mycf2', SPLITS=['aaa', 'bbb', 'ccc']
1.2.2 Region的強制拆分
除了預拆分和自動拆分以外,你還可以對運行了一段時間的Region 進行強制地手動拆分(forced splits),方法是呼叫hbase shell的 split方法,split的呼叫方式如下:
split ‘regionName’ # format: ‘tableName,startKey,id’
比如:
split 'test_table1,c,1476406588669.96dd8c68396fda69'
這個就是把test_table1,c,1476406588669.96dd8c68396fda69這個 Region從新的拆分點999處拆成2個Region,
1.3 Region拆分推薦的方案
一開始可以先定義拆分點,但是當資料開始作業起來后會出現熱點 不均的情況,所以推薦的方法是:
(1)用預拆分匯入初始資料,
(2)然后用自動拆分來讓HBase來自動管理Region,
建議:不要關閉自動拆分
1.4 Region的合并
Region可以被拆分,也可以被合并,不過Region的合并(merge) 并不是為了性能考慮的,而更多地是出于維護的目的被創造出來的,
什么時候會用到Merger呢?比如你刪了大量的資料,每個Region都變小了,這個時候分成這么 多個Region就有點浪費了,可以把Region合并起來,然后可以減少一些 RegionServer服務器來節省成本,
1.4.1 通過Merger類合并Region
合并通過使用org.apache.hadoop.hbase.util.Merge類來實作,
舉個例子,比如我想把以下兩個Region合并:
test_table1,b,1476406588669.39eecae03539ba0a63264c24130c2cb1. test_table1,c,1476406588669.96dd8c68396fda694ab9b0423a60a4d9.
就需要在Linux下(不需要進入hbase shell)執行以下命令:
hbase org.apache.hadoop.hbase.util.Merge test_table1
test_table1,b,1476406588669.39eecae03539ba0a63264c24130c2cb1. test_table1,c,1476406588669.96dd8c68396fda694ab9b0423a60a4d9.
但是這種方法在執行命令前得先把集群給下線了才行,所以我們需要先把HMaster和所有的 HRegionServer全部都停掉,不過每次merge都要關閉整個HBase太麻煩了,所以后來HBase 又增加了online_merge(熱合并),
1.4.2 熱合并
hbase shell提供了一個命令叫online_merge,通過這個方法可以 進行熱合并(online_merge),舉個例子:
假設要合并以下兩個Region: test_table1,a,1476406588669.d1f84781ec2b93224528cbb79107ce12. test_table1,b,1476408648520.d129fb5306f604b850ee4dc7aa2eed36. online_merge的傳參是Region的hash值,Region的hash值就是 Region名最后那段在兩個.號之間的字串,
需要在hbase shell 中執行以下命令:
merge_region
'd1f84781ec2b93224528cbb79107ce12', 'd129fb5306f604b850ee4dc7aa2eed36'
二、 Region的自動平衡
HBase 的 Region 分配和自動均衡是由 Master 節點控制的,在初始化表時會先分配一個Region,然后指定給某個Region Server, 如果使用預磁區,那么Master 會按照輪詢的方式平均分配到每個 Region Server,此后,隨著Region不斷的增大和裂變,Region Server 上的 Region 數量開始變得不均衡,如果開啟了自動均衡開關,Master 會通過定時器來檢查集群中的Regions在各個RegionServer之間的負載是否是均衡,一旦檢測到不均衡的情況,就會生成相應的Region遷移計劃,
關于均衡的方式,HBase 提供以下兩種策略:
- DefaultLoadBalancer 默認的策略,根據 Region 個數來進行均衡
- StochasticLoadBalancer 根據讀寫壓力評估來進行均衡
由于HBase的的資料(包括HLog、StoreFile等)都是寫入到HDFS檔案系統中的, 因此 HBase 的 Region 遷移是非常輕量級的,在做Region遷移時,Region所對應的HDFS檔案是不變的,此時只需要將 Region 的元資料重新分配到目標 Region Server 就可以了,遷移程序的步驟包含:
- 創建Region 遷移計劃,指定 RegionID、源 Region Server 和目標 Region Server;
- 源 Region Server 解綁,此時會關閉 Region;
- 目標 Region Server 系結,重新打開 Region;
三、 HFile的合并
上一篇文章中,我們從RegionServer一直介紹到了HFile的格式,稍微復習一下:

在結尾的資料塊中包含了資料相關的索引資訊,系統也是通過結尾的索引資訊找到 HFile 中的資料,HFile 中的資料塊大小默認為 64KB,如果訪問 HBase 資料庫的場景多為有序的訪問,那么建議將該值設定的大一些,如果場景多為隨機訪問,那么建議將該值設定的小一些,一般情況下,通過調整該值可以提高 HBase 的性能,
好,那我們接下來看HFile的compaction(整理合并),HFile(StoreFile是HFile的Java抽象物件)是會經常被合并和拆分的,為什么要合并?每次memstore的刷寫都會產生一 個新的HFile,而HFile畢竟是存盤在硬碟上的東西,凡是讀取存盤在硬 盤上的東西都涉及一個操作:尋址,如果是傳統硬碟那就是磁頭的移動 尋址,這是一個很慢的動作,當HFile一多,你每次讀取資料的時候尋 址的動作就多了,效率就低了,所以為了防止尋址的動作過多,我們要 適當地減少碎片檔案,所以需要合并操作,HFile合并操作就是在一個Store里面找到需要合并的HFile,然后把他們合并起來,最后把之前的碎檔案移除,那么問題就來了:哪些文 件需要被合并?
3.1 合并的策略
HFile的合并有很多種策略,不同策略之間的區別主要就是對于要
合并的檔案集合的定義方法,如果你去搜索hbase compaction,首先會看到的就是兩個概念:Minor Compaction和M ajor Compaction:
-
Minor Compaction:將Store中多個HFile合并為一個HFile,在這個程序中達到TTL的資料會被移除,但是被手動洗掉的資料不會被移除,這種合并觸發頻率較高,
-
Major Compaction:合并Store中的所有HFile為一個HFile,在 這個程序中被手動洗掉的資料會被真正地移除,同時被洗掉的還有單元格內超過MaxVersions的版本資料,這種合并觸發頻率較 低,默認為7天一次,不過由于Major Compaction消耗的性能較 大,你不會想讓它發生在業務高峰期,建議手動控制Major Compaction的時機,
Major Compaction是把一個Store中的HFile合并為一個HFile,很多 網上的資料說Major Compaction是把一個Region中的所有HFile合并 成一個檔案,這是錯的,
在0.96版本之前的Minor Compaction策略是 RatioBasedCompactionPolicy,0.96版本之后,出現了多種Minor Compaction策略,
①RatioBasedCompactionPolicy
從舊到 新地掃描HFile檔案,當掃描到某個檔案,該檔案滿足以下條件:
該檔案的大小< 比它更新的所有檔案的大小總和 * hbase.store.compaction.ratio
滿足條件以后把該HFile和比它更新的所有HFile合并成一個 HFile,
② ExploringCompactionPolicy
0.96版本之后提出了ExploringCompactionPolicy演算法,并且把該演算法作為了默認演算法,這個演算法主要做了如下改動:
不再武斷地認為,某個檔案滿足條件就把更新的檔案全部合并進去,確切地說,現在的遍歷不強調順序性了,是把所有的檔案都遍歷一 遍之后每一個檔案都去考慮,符合條件而進入待合并串列的檔案由新的條件判斷:
該檔案< (所有檔案大小總和 - 該檔案大小) * 比例因子
③ FIFOCompactionPolicy
④ StripeCompactionPolicy
這些策略的詳細程序可以參閱《HBase不睡覺書》以及HBase原始碼,讀者可以自己進行拓展,
3.2 合并時的步驟
合并經歷了以下幾個具體步驟:
(1)獲取需要合并的HFile串列,
(2)由串列創建出StoreFileScanner,
HRegion會創建出一個Scanner,用這個Scanner來讀取本次要合并 的所有StoreFile上的資料,
(3)把資料從這些HFile中讀出,并放到tmp目錄(臨時檔案 夾),
HBase會在臨時目錄中創建新的HFile,并使用之前建立的Scanner 從舊HFile上讀取資料,放入新HFile,以下兩種資料不會被讀取出來:
- 如果資料過期了(達到TTL所規定的時間),那么這些資料不會 被讀取出來,
- 如果是majorCompaction,那么資料帶了墓碑標記也不會被讀取 出來,
(4)用合并后的HFile來替換合并前的那些HFile,
最后用臨時檔案夾內合并后的新HFile來替換掉之前的那些HFile文 件,過期的資料由于沒有被讀取出來,所以就永遠地消失了,如果本次 合并是Major Compaction,那么帶有墓碑標記的檔案也因為沒有被讀取 出來,就真正地被洗掉掉了,
3.3 Major Compaction
-
Major Compaction的目的:
Minor Compaction的目的是增加讀性能,而major Compaction在 minorCompaction的目的之上還增加了一點:真正地從磁盤上把用戶洗掉的資料(帶墓碑標記的資料)洗掉掉, -
為什么只有major Compaction可以真正洗掉資料?
其實HBase一直拖到major Compaction的時候才真正把帶墓碑標記的 資料刪掉,并不是因為性能要求,而是之前真的做不到,之前提到過 HBase是建立在HDFS這種只有增加洗掉而沒有修改的檔案系統之上的, 所以就連用戶洗掉這個動作,在底層都是由新增實作的: -
用戶增加一條資料就在HFile上增加一條KeyValue,型別是PUT,
-
用戶洗掉一條資料還是在HFile上增加一條KeyValue,型別是 DELETE,這就是墓碑標記,
現在會遇到一個問題:當用戶洗掉資料的時候之前的資料已經被刷 寫到磁盤上的另外一個HFile了,這種情況很常見,也就是說,墓碑標記和原始資料這兩個KeyValue壓根就不在同一個HFile上,如下圖所示,

在查詢的時候Scan指標其實是把所有的HFile都看過了一遍,它知道了有這條資料,也知道它有墓碑標記,而在回傳資料的時候選擇不把
資料回傳給用戶,這樣在用戶的Scan操作看來這條資料就是被刪掉了, 如果你可以帶上RAW=>true引數來Scan,你就可以查詢到這條被打上墓碑標記的資料,
- 為什么達到TTL的資料可以被Minor Compaction洗掉?
這是因為當資料達到TTL的時候,并不需要額外的一個KeyValue來 記錄,合并時創建的Scanner在查詢資料的時候,根據以下公式來判斷cell 是否過期:
當前時間now - cell的時間戳 > TTL
如果過期了就不回傳這條資料,這樣當合并完成后,過期的資料因為沒有被寫入新檔案,自然就消失了,
- Major Compaction是怎么產生的?
它其實就是Minor Compaction升級而來的,如果本次Minor Compaction包含所有文 件,并且達到了足夠的時間間隔,則會被升級為Major Compaction,判 斷是否包含所有檔案比較簡單,判斷是否達到了足夠的時間間隔則需要 根據以下兩個配置項綜合考慮:
- hbase.hregion.majorcompaction:major Compaction發生的周期,單位是毫秒,默認值是7天,
- hbase.hregion.majorcompaction.jitter majorCompaction:周期抖動引數,0~1.0的一個指數,調整這個引數可以讓Major Compaction的發生時間更靈活,默認值是0.5,
提示:
雖然有以上機制控制Major Compaction的發生時機,但是由于Major Compaction時對系統的壓力還是很大的,所以建議關閉自動Major Compaction,采用手動觸發的方式,定期進行Major Compaction,
四、BlockCache 和 布隆過濾器
4.1 BlockCache
一個RegionServer只有一個 BlockCache,之前畫的架構圖上是沒有標出BlockCache的,這是因為之 前的圖上出現的都是資料存盤必需的組成部分,而BlockCache不是資料 存盤的必須組成部分,他只是用來優化讀取性能的,如果加上 BlockCache,之前的架構就變成如下圖所示:

BlockCache名稱中的Block指的是HBase的Block,BlockCache的作業原理跟其他快取一樣:
讀請求到HBase之 后先嘗試查詢BlockCache,如果獲取不到就去HFile(StoreFile)和 Memstore中去獲取,如果獲取到了則在回傳資料的同時把Block塊快取 到BlockCache中,BlockCache默認是開啟的,
接下來看看有哪幾種BlockCache的實作方案:
①LRUBlock Cache
近期最少使用演算法,讀出來的block會被放到BlockCache中待 下次查詢使用,當快取滿了的時候,會根據LRU的演算法來淘汰block,

②SlabCache
這是一種堆外記憶體的解決方案,
堆外記憶體(off-heap memory)是 不屬于JVM管理的記憶體范圍,說白了,就是原始的記憶體區域了,堆外內 存的大小可以通過-XX:MaxDirectMe morySize=60MB這樣來設定,
使用堆外記憶體最大的好處就是:回收堆外記憶體的時候JVM幾乎不會停頓,這樣再也不用怕回收的時候業務系統卡住了,既然堆外記憶體回收的時候不會卡,為什么大家不都去用它呀?這是因為堆外 記憶體的缺點幾乎比它帶來的好處還大:
- 因為在堆外記憶體存盤的資料都是很原始的資料,如果是一個對 象,比如先序列化之后才能存盤,所以不能存盤太復雜的物件,
- 堆外記憶體并不是在JVM的管理范圍,所以當記憶體泄露的時候很不 好排查問題,
- 堆外記憶體由于用的是系統記憶體,當你用的太大的時候,物理記憶體有可能爆掉,或者直接開啟了虛擬記憶體,也就是直接影響到了硬碟的使用,
再來說下SlabCache的具體實作,SlabCache呼叫了nio的 DirectByteBuffers,SlabCahce把堆外記憶體按照80%和20%的比例劃分為 兩個區域:
(1)存放大小約等于1個BlockSize默認值的Block,
(2)存放大小約等于2個BlockSize默認值的Block,
③Bucket Cache
BucketCache借鑒了SlabCache的創意,也用上了堆外記憶體,不過它有以下自己的特點:
-
相比起只有2個區域的SlabeCache,BucketCache一上來就分配了 14種區域,這 14種區域分別放的是大小為4KB、8KB、16KB、32KB、40KB、 48KB、56KB、64KB、96KB、128KB、192KB、256KB、384KB、 512KB的Block,而且這個種類串列還是可以手動通過設定 hbase.bucketcache.bucket.sizes屬性來定義
-
BucketCache的存盤不一定要使用堆外記憶體,是可以自由在3種存 儲介質直接選擇:堆(heap)、堆外(offheap)、檔案 (file),通過設定hbase.bucketcache.ioengine為heap、 offfheap或者file來配置,
-
每個Bucket的大小上限為最大尺寸的block * 4,比如可以容納 的最大的Block型別是512KB,那么每個Bucket的大小就是512KB * 4 = 2048KB,
-
系統一啟動BucketCache就會把可用的存盤空間按照每個Bucket 的大小上限均分為多個Bucket,如果劃分完的數量比你的種類還 少,比如比14(默認的種類數量)少,就會直接報錯,因為每一 種型別的Bucket至少要有一個Bucket,
④組合模式
具體地說就是把不同型別的Block分別放到 LRUCache和BucketCache中,
Index Block和Bloom Block會被放到LRUCache中,Data Block被直 接放到BucketCache中,所以資料會去LRUCache查詢一下,然后再去 BucketCache中查詢真正的資料,其實這種實作是一種更合理的二級緩 存,資料從一級快取到二級快取最后到硬碟,資料是從小到大,存盤介 質也是由快到慢,考慮到成本和性能的組合,比較合理的介質是: LRUCache使用記憶體->BuckectCache使用SSD->HFile使用機械硬碟,
4.2 BloomFilter
布隆過濾器是hbase中的高級功能,它能夠減少特定訪問模式(get/scan)下的查詢時間,不過由于這種模式增加了記憶體和存盤的負擔,所以被默認為關閉狀態,
hbase支持如下型別的布隆過濾器:
1、NONE 不使用布隆過濾器
2、ROW 行鍵使用布隆過濾器
3、ROWCOL 列鍵使用布隆過濾器
其中ROWCOL是粒度更細的模式,
- 為什么要引入布隆過濾器呢?
如果用戶隨機查找一個行鍵,則這個行鍵很可能位于兩個開始鍵(即索引)之間的位置,對于hbase來說,它判斷這個行鍵是否真實存在的唯一方法就是加載這個資料塊,并且掃描它是否包含這個鍵,
當我們隨機讀get資料時,如果采用hbase的塊索引機制,hbase會加載很多塊檔案,
如果采用布隆過濾器后,它能夠準確判斷該HFile的所有資料塊中,是否含有我們查詢的資料,從而大大減少不必要的塊加載,從而增加hbase集群的吞吐率,
1、布隆過濾器的存盤在哪?
對于hbase而言,當我們選擇采用布隆過濾器之后,HBase會在生成StoreFile(HFile)時包含一份布隆過濾器結構的資料,稱其為MetaBlock;MetaBlock與DataBlock(真實的KeyValue資料)一起由LRUBlockCache維護,所以,開啟bloomfilter會有一定的存盤及記憶體cache開銷,但是在大多數情況下,這些負擔相對于布隆過濾器帶來的好處是可以接受的,
2、采用布隆過濾器后,hbase如何get資料?
在讀取資料時,hbase會首先在布隆過濾器中查詢,根據布隆過濾器的結果,再在MemStore中查詢,最后再在對應的HFile中查詢,
3、采用ROW還是ROWCOL布隆過濾器?
這取決于用戶的使用模式,如果用戶只做行掃描,使用更加細粒度的行加列布隆過濾器不會有任何的幫助,這種場景就應該使用行級布隆過濾器,當用戶不能批量更新特定的一行,并且最后的使用存盤檔案都含有改行的一部分時,行加列級的布隆過濾器更加有用,
參考
《HBase不睡覺書》
https://blog.csdn.net/qq_38180223/article/details/80922114
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/302738.html
標籤:其他
上一篇:關于在Ubuntu系統下JDBC連接MySQL資料庫時出現錯誤 Access denied for user ‘root‘@‘localhost‘
