HBase 優化

HBase 優化

JVM調優

內存調優

通常安裝好的HBase集羣,默認配置是給Master和RegionServer 1G的內存,而Memstore默認佔0.4,也就是400MB。顯然RegionServer給的1G真的太少了。java

export HBASE_MASTER_OPTS="$HBASE_MASTER_OPTS -Xms2g -Xmx2g"
export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS -Xms8g -Xmx8g"

這裏只是舉例,並非全部的集羣都是這麼配置。
==要牢記至少留10%的內存給操做系統來進行必要的操做==算法

如何給出一個合理的JVM 內存大小設置,舉一個ambari官方提供的例子吧。windows

好比你如今有一臺16GB的機器,上面有MapReduce服務、 RegionServer和DataNode(這三位通常都是裝在一塊兒的),那麼建議按 照以下配置設置內存:緩存

  • 2GB:留給系統進程。
  • 8GB:MapReduce服務。平均每1GB分配6個Map slots + 2個Reduce slots。
  • 4GB:HBase的RegionServer服務
  • 1GB:TaskTracker
  • 1GB:DataNode

若是同時運行MapReduce的話,RegionServer將是除了MapReduce之外使用內存最大的服務。若是沒有MapReduce的話,RegionServer能夠調整到大概一半的服務器內存。服務器

Full GC調優

因爲數據都是在RegionServer裏面的,Master只是作一些管理操做,因此通常內存問題都出在RegionServer上。併發

JVM提供了4種GC回收器:性能

  • 串行回收器(SerialGC)。
  • 並行回收器(ParallelGC),主要針對年輕帶進行優化(JDK 8 默認策略)。
  • 併發回收器(ConcMarkSweepGC,簡稱CMS),主要針對年老帶進 行優化。
  • G1GC回收器,主要針對大內存(32GB以上才叫大內存)進行優化。

通常會採起兩種組合方案測試

  1. ParallelGC和CMS的組合方案

    export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS -Xms8g -Xmx8g -XX:+UseParNewGC -XX:+UseConMarkSweepGC"優化

  2. G1GC方案

    export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS -Xms8g -Xmx8g -XX:+UseG1GC -XX:MaxGCPauseMillis=100"spa

怎麼選擇呢?

通常內存很大(32~64G)的時候,纔會去考慮用G1GC方案。
若是你的內存小於4G,乖乖選擇第一種方案吧。
若是你的內存(4~32G)之間,你須要自行測試下兩種方案,孰強孰弱靠實踐。測試的時候記得加上命令
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy

MSLAB和In Memory Compaction(HBase2.X纔有)

HBase本身實現了一套以Memstore爲最小單元的內存管理機制,稱爲 MSLAB(Memstore-Local Allocation Buffers)

跟MSLAB相關的參數是:

  • hbase.hregion.memstore.mslab.enabled:設置爲true,即打開 MSLAB,默認爲true。
  • hbase.hregion.memstore.mslab.chunksize:每一個chunk的大 小,默認爲2048 * 1024 即2MB。
  • hbase.hregion.memstore.mslab.max.allocation:能放入chunk 的最大單元格大小,默認爲256KB,已經很大了。
  • hbase.hregion.memstore.chunkpool.maxsize:在整個memstore 能夠佔用的堆內存中,chunkPool佔用的比例。該值爲一個百分 比,取值範圍爲0.0~1.0。默認值爲0.0。 hbase.hregion.memstore.chunkpool.initialsize:在 RegionServer啓動的時候能夠預分配一些空的chunk出來放到 chunkPool裏面待使用。該值就表明了預分配的chunk佔總的 chunkPool的比例。該值爲一個百分比,取值範圍爲0.0~1.0,默認值爲0.0。

在HBase2.0版本中,爲了實現更高的寫入吞吐和更低的延遲,社區團隊對MemStore作了更細粒度的設計。這裏,主要指的就是In Memory Compaction。

開啓的條件也很簡單。

hbase.hregion.compacting.memstore.type=BASIC # 可選擇NONE/BASIC/EAGER

具體這裏不介紹了。

Region自動拆分

Region的拆分分爲自動拆分和手動拆分。自動拆分能夠採用不一樣的策略。

拆分策略

ConstantSizeRegionSplitPolicy

0.94版本的策略方案

hbase.hregion.max.filesize

經過該參數設定單個Region的大小,超過這個閾值就會拆分爲兩個。

IncreasingToUpperBoundRegionSplitPolicy(默認)

文件尺寸限制是動態的,依賴如下公式來計算

Math.min(tableRegionCount^3 * initialSize, defaultRegionMaxFileSize)

  • tableRegionCount:表在全部RegionServer上所擁有的Region數量總和。
  • initialSize:若是你定義了 hbase.increasing.policy.initial.size,則使用這個數值;若是沒有定義,就用memstore的刷寫大小的2倍,即 hbase.hregion.memstore.flush.size * 2。
  • defaultRegionMaxFileSize:ConstantSizeRegionSplitPolicy 所用到的hbase.hregion.max.filesize,即Region最大大小。

假如hbase.hregion.memstore.flush.size定義爲128MB,那麼文件 尺寸的上限增加將是這樣:

  1. 剛開始只有一個文件的時候,上限是256MB,由於1^3 1282 = 256MB。
  2. 當有2個文件的時候,上限是2GB,由於2^3 128 2 2048MB。
  3. 當有3個文件的時候,上限是6.75GB,由於3^3 128 2 = 6912MB。
  4. 以此類推,直到計算出來的上限達到 hbase.hregion.max.filesize所定義的10GB。

KeyPrefixRegionSplitPolicy

除了簡單粗暴地根據大小來拆分,咱們還能夠本身定義拆分點。 KeyPrefixRegionSplitPolicy 是 IncreasingToUpperBoundRegionSplitPolicy的子類,在前者的基礎上增長了對拆分點(splitPoint,拆分點就是Region被拆分處的rowkey)的定義。它保證了有相同前綴的rowkey不會被拆分到兩個不一樣的Region裏面。這個策略用到的參數是KeyPrefixRegionSplitPolicy.prefix_length rowkey:前綴長度

那麼它與IncreasingToUpperBoundRegionSplitPolicy區別,用兩張圖來看。

默認策略爲

image.png

KeyPrefixRegionSplitPolicy策略

image.png

若是你的前綴劃分的比較細,你的查詢就比較容易發生跨Region查詢的狀況,此時採用KeyPrefixRegionSplitPolicy較好。

因此這個策略適用的場景是:

  • 數據有多種前綴。
  • 查詢可能是針對前綴,比較少跨越多個前綴來查詢數據。

DelimitedKeyPrefixRegionSplitPolicy

該策略也是繼承自IncreasingToUpperBoundRegionSplitPolicy,它也是根據你的rowkey前綴來進行切分的。惟一的不一樣就是: KeyPrefixRegionSplitPolicy是根據rowkey的固定前幾位字符來進行判斷,而DelimitedKeyPrefixRegionSplitPolicy是根據分隔符來判斷的。在有些系統中rowkey的前綴可能不必定都是定長的。

使用這個策略須要在表定義中加入如下屬性:

DelimitedKeyPrefixRegionSplitPolicy.delimiter:前綴分隔符

好比你定義了前綴分隔符爲_,那麼host1_001和host12_999的前綴就分別是host1和host12。

BusyRegionSplitPolicy

若是你的系統經常會出現熱點Region,而你對性能有很高的追求, 那麼這種策略可能會比較適合你。它會經過拆分熱點Region來緩解熱點 Region的壓力,可是根據熱點來拆分Region也會帶來不少不肯定性因 素,由於你也不知道下一個被拆分的Region是哪一個。

DisabledRegionSplitPolicy

這種策略就是Region永不自動拆分。

若是你事先就知道這個Table應該按 怎樣的策略來拆分Region的話,你也能夠事先定義拆分點 (SplitPoint)。所謂拆分點就是拆分處的rowkey,好比你能夠按26個 字母來定義25個拆分點,這樣數據一到HBase就會被分配到各自所屬的 Region裏面。這時候咱們就能夠把自動拆分關掉,只用手動拆分。

手動拆分有兩種狀況:預拆分(pre-splitting)和強制拆分 (forced splits)。

推薦方案

一開始能夠先定義拆分點,可是當數據開始工做起來後會出現熱點 不均的狀況,因此推薦的方法是:

  1. 用預拆分導入初始數據。
  2. 而後用自動拆分來讓HBase來自動管理Region。

==建議:不要關閉自動拆分。==

Region的拆分對性能的影響仍是很大的,默認的策略已經適用於大 多數狀況。若是要調整,儘可能不要調整到特別不適合你的策略

BlockCache優化

一個RegionServer只有一個BlockCache。

BlockCache的工做原理:讀請求到HBase以後先嚐試查詢BlockCache,若是獲取不到就去HFile(StoreFile)和Memstore中去獲取。若是獲取到了則在返回數據的同時把Block塊緩存到BlockCache中。它默認是開啓的。

若是你想讓某個列簇不使用BlockCache,能夠經過如下命令關閉它。

alter 'testTable', CONFIGURATION=>{NAME => 'cf',BLOCKCACHE=>'false'}

BlockCache的實現方案有

  • LRU BLOCKCACHE
  • SLAB CACHE
  • Bucket CACHE

LRU BLOCKCACHE

在0.92版本 以前只有這種BlockCache的實現方案。LRU就是Least Recently Used, 即近期最少使用算法的縮寫。讀出來的block會被放到BlockCache中待 下次查詢使用。當緩存滿了的時候,會根據LRU的算法來淘汰block。 LRUBlockCache被分爲三個區域,

image.png

看起來是否是很像JVM的新生代、年老代、永久代?沒錯,這個方案就是模擬JVM的代設計而作的。

Slab Cache

SlabCache實際測試起來對Full GC的改善很小,因此這個方案最後被廢棄了。不過它被廢棄還有一個更大的緣由,這就是有另外一個更好的Cache方案產生了,也用到了堆外內存,它就是BucketCache。

Bucket Cache

  • 相比起只有2個區域的SlabeCache,BucketCache一上來就分配了 14種區域。注意:我這裏說的是14種區域,並非14塊區域。這 14種區域分別放的是大小爲4KB、8KB、16KB、32KB、40KB、 48KB、56KB、64KB、96KB、128KB、192KB、256KB、384KB、 512KB的Block。並且這個種類列表仍是能夠手動經過設置 hbase.bucketcache.bucket.sizes屬性來定義(種類之間用逗號 分隔,想配幾個配幾個,不必定是14個!),這14種類型能夠分 配出不少個Bucket。
  • BucketCache的存儲不必定要使用堆外內存,是能夠自由在3種存 儲介質直接選擇:堆(heap)、堆外(offheap)、文件 (file,這裏的文件能夠理解成SSD硬盤)。經過設置hbase.bucketcache.ioengine爲heap、 offfheap或者file來配置。
  • 每一個Bucket的大小上限爲最大尺寸的block 4,好比能夠容納的最大的Block類型是512KB,那麼每一個Bucket的大小就是512KB 4 = 2048KB。
  • 系統一啓動BucketCache就會把可用的存儲空間按照每一個Bucket 的大小上限均分爲多個Bucket。若是劃分完的數量比你的種類還少,好比比14(默認的種類數量)少,就會直接報錯,由於每一種類型的Bucket至少要有一個Bucket。

image.png

Bucket Cache默認也是開啓的,若是要關閉的話

alter 'testTable', CONFIGURATION=>{CACHE_DATA_IN_L1 => 'true'}

它的配置項:

  • hbase.bucketcache.ioengine:使用的存儲介質,可選值爲 heap、offheap、file。不設置的話,默認爲offheap。
  • hbase.bucketcache.combinedcache.enabled:是否打開組合模 式(CombinedBlockCache),默認爲true
  • hbase.bucketcache.size:BucketCache所佔的大小
  • hbase.bucketcache.bucket.sizes:定義全部Block種類,默認 爲14種,種類之間用逗號分隔。單位爲B,每一種類型必須是 1024的整數倍,不然會報異常:java.io.IOException: Invalid HFile block magic。默認值爲:四、八、1六、3二、40、4八、5六、 6四、9六、12八、19二、25六、38四、512。
  • -XX:MaxDirectMemorySize:這個參數不是在hbase-site.xml中 配置的,而是JVM啓動的參數。若是你不配置這個參數,JVM會按 需索取堆外內存;若是你配置了這個參數,你能夠定義JVM能夠得到的堆外內存上限。顯而易見的,這個參數值必須比 hbase.bucketcache.size大。

在SlabCache的時代,SlabCache,是跟LRUCache一塊兒使用的,每一 個Block被加載出來都是緩存兩份,一份在SlabCache一份在LRUCache, 這種模式稱之爲DoubleBlockCache。讀取的時候LRUCache做爲L1層緩存 (一級緩存),把SlabCache做爲L2層緩存(二級緩存)。

在BucketCache的時代,也不是單純地使用BucketCache,可是這回 不是一二級緩存的結合;而是另外一種模式,叫組合模式 (CombinedBlockCahce)。具體地說就是把不一樣類型的Block分別放到 LRUCache和BucketCache中。

Index Block和Bloom Block會被放到LRUCache中。Data Block被直 接放到BucketCache中,因此數據會去LRUCache查詢一下,而後再去 BucketCache中查詢真正的數據。其實這種實現是一種更合理的二級緩 存,數據從一級緩存到二級緩存最後到硬盤,數據是從小到大,存儲介質也是由快到慢。考慮到成本和性能的組合,比較合理的介質是: LRUCache使用內存->BuckectCache使用SSD->HFile使用機械硬盤。

總結

關於LRUBlockCache和BucketCache單獨使用誰比較強,曾經有人作 過一個測試。

  • 由於BucketCache本身控制內存空間,碎片比較少,因此GC時間 大部分都比LRUCache短。
  • 在緩存所有命中的狀況下,LRUCache的吞吐量是BucketCache的 兩倍;在緩存基本命中的狀況下,LRUCache的吞吐量跟 BucketCache基本相等。
  • 讀寫延遲,IO方面二者基本相等。
  • 緩存所有命中的狀況下,LRUCache比使用fiile模式的 BucketCache CPU佔用率低一倍,可是跟其餘狀況下差很少。

從總體上說LRUCache的性能好於BucketCache,但因爲Full GC的存在,在某些時刻JVM會中止響應,形成服務不可用。因此適當的搭配 BucketCache能夠緩解這個問題。

HFile合併

合併分爲兩種操做:

  • Minor Compaction:將Store中多個HFile合併爲一個HFile。在 這個過程當中達到TTL的數據會被移除,可是被手動刪除的數據不 會被移除。這種合併觸發頻率較高。
  • Major Compaction:合併Store中的全部HFile爲一個HFile。在 這個過程當中被手動刪除的數據會被真正地移除。同時被刪除的還 有單元格內超過MaxVersions的版本數據。這種合併觸發頻率較 低,默認爲7天一次。不過因爲Major Compaction消耗的性能較 大,你不會想讓它發生在業務高峯期,建議手動控制Major Compaction的時機。

Compaction合併策略

RatioBasedCompactionPolicy

從舊到新地掃描HFile文件,當掃描到某個文件,該文件知足如下條件:

該文件大小 < 比它更新的全部文件的大小總和 * hbase.store.compation.ratio(默認1.2)

實際狀況下的RatioBasedCompactionPolicy算法效果不好,常常引 發大面積的合併,而合併就不能寫入數據,常常由於合併而影響IO。所 以HBase在0.96版本以後修改了合併算法。

ExploringCompactionPolicy

0.96版本以後提出了ExploringCompactionPolicy算法,而且把該 算法做爲了默認算法。

算法變動爲

該文件大小 < (全部文件大小總和 - 該文件大小) * hbase.store.compation.ratio(默認1.2)

若是該文件大小小於最小合併大小(minCompactSize),則連上面那個公式都不須要套用,直接進入待合併列表。最小合併大小的配置項:hbase.hstore.compaction.min.size。若是沒設定該配置項,則使用hbase.hregion.memstore.flush.size。

被挑選的文件必須能經過以上提到的篩選條件,而且組合內含有的文件數必須大於hbase.hstore.compaction.min,小於 hbase.hstore.compaction.max。

文件太少了不必合併,還浪費資源;文件太多了太消耗資源,怕 機器受不了。

挑選完組合後,比較哪一個文件組合包含的文件更多,就合併哪一個組 合。若是出現平局,就挑選那個文件尺寸總和更小的組合。

FIFOCompactionPolicy

這個合併算法實際上是最簡單的合併算法。嚴格地說它都不算是一種合併算法,是一種刪除策略。

FIFOCompactionPolicy策略在合併時會跳過含有未過時數據的 HFile,直接刪除全部單元格都過時的塊。最終的效果是:

  • 過時的塊被整個刪除掉了。
  • 沒過時的塊徹底沒有操做。

這個策略不能用於什麼狀況

  1. 表沒有設置TTL,或者TTL=FOREVER。
  2. 表設置了MIN_VERSIONS,而且MIN_VERSIONS > 0

DateTieredCompactionPolicy

DateTieredCompactionPolicy解決的是一個基本的問題:最新的數據最 有可能被讀到。

配置項

  • hbase.hstore.compaction.date.tiered.base.window.millis: 基本的時間窗口時長。默認是6小時。拿默認的時間窗口舉例:從如今到6小時以內的HFile都在同一個時間窗口裏 面,即這些文件都在最新的時間窗口裏面。
  • hbase.hstore.compaction.date.tiered.windows.per.tier:層 次的增加倍數。分層的時候,越老的時間窗口越寬。在同一個窗口裏面的文件若是達到最小合併數量(hbase.hstore.compaction.min)就會進行合併,但不 是簡單地合併成一個,而是根據 hbase.hstore.compaction.date.tiered.window.policy.class 所定義的合併規則來合併。說白了就是,具體的合併動做 使用的是用前面提到的合併策略中的一種(我剛開始看到 這個設計的時候都震撼了,竟然能夠策略套策略),默認是ExploringCompactionPolicy。
  • hbase.hstore.compaction.date.tiered.max.tier.age.millis: 最老的層次時間。當文件太老了,老到超過這裏所定義的時間範 圍(以天爲單位)就直接不合並了。不過這個設定會帶來一個缺 點:若是Store裏的某個HFile太老了,可是又沒有超過TTL,並 且大於了最老的層次時間,那麼這個Store在這個HFile超時被刪 除前,都不會發生Major Compaction。沒有Major Compaction, 用戶手動刪除的數據就不會被真正刪除,而是一直佔着磁盤空間。

配置項好像很複雜的樣子,舉個例子畫個圖就清楚了。

假設基本窗口寬度 (hbase.hstore.compaction.date.tiered.base.window.millis) = 1。 最小合併數量(hbase.hstore.compaction.min) = 3。 層次增加倍數 (hbase.hstore.compaction.date.tiered.windows.per.tier) = 2。

image.png

這個策略很是適用於什麼場景

  • 常常讀寫最近數據的系統,或者說這個系統專一於最新的數據。
  • 由於該策略有可能引起不了Major Compaction,沒有Major Compaction是沒有辦法刪除掉用戶手動刪除的信息,因此更適用 於那些基本不刪除數據的系統。

這個策略比較適用於什麼場景

  • 數據根據時間排序存儲。
  • 數據的修改頻率頗有限,或者只修改最近的數據,基本不刪除數據。

這個策略不適用於什麼場景

  • 數據改動很頻繁,而且連很老的數據也會被頻繁改動。
  • 常常邊讀邊寫數據。

StripeCompactionPolicy

該策略在讀取方面穩定。

image.png

那麼什麼場景適合用StripeCompactionPolicy

  • Region要夠大:這種策略實際上就是把Region給細分紅一個個 Stripe。Stripe能夠看作是小Region,咱們能夠管它叫sub- region。因此若是Region不大,不必用Stripe策略。小Region 用Stripe反而增長IO負擔。多大才算大?做者建議若是Region大 小小於2GB,就不適合用StripeCompactionPolicy。
  • Rowkey要具備統一格式,可以均勻分佈。因爲要劃分KeyRange, 因此key的分佈必須得均勻,好比用26個字母打頭來命名 rowkey,就能夠保證數據的均勻分佈。若是使用timestamp來作 rowkey,那麼數據就無法均勻分佈了,確定就不適合使用這個策略。

總結

請詳細地看各類策略的適合場景,並根據場景選擇策略。

  • 若是你的數據有固定的TTL,而且越新的數據越容易被讀到,那麼DateTieredCompaction通常是比較適合你的。
  • 若是你的數據沒有TTL或者TTL較大,那麼選擇StripeCompaction會比默認的策略更穩定。
  • FIFOCompaction通常不會用到,這只是一種極端狀況,好比用於 生存時間特別短的數據。若是你想用FIFOCompaction,能夠先考慮使用DateTieredCompaction。

附錄

相關文章
相關標籤/搜索