HBase Memstore
首先通過簡單介紹HBase的讀寫過程來理解一下MemStore到底是什么,在何處發揮作用,如何使用到以及為什么要用MemStore。
圖一:Memstore Usage in HBase Read/Write Paths
當RegionServer(RS)收到寫請求的時候(write request),RS會將請求轉至相應的Region。每一個Region都存儲着一些列(a set of rows)。根據其列族的不同,將這些列數據存儲在相應的列族中(Column Family,簡寫CF)。不同的CFs中的數據存儲在各自的HStore中,HStore由一個Memstore及一系列HFile組成。Memstore位於RS的主內存中,而HFiles被寫入到HDFS中。當RS處理寫請求的時候,數據首先寫入到Memstore,然后當到達一定的閥值的時候,Memstore中的數據會被刷到HFile中。
用到Memstore最主要的原因是:存儲在HDFS上的數據需要按照row key 排序。而HDFS本身被設計為順序讀寫(sequential reads/writes),不允許修改。這樣的話,HBase就不能夠高效的寫數據,因為要寫入到HBase的數據不會被排序,這也就意味着沒有為將來的檢索優化。為了解決這個問題,HBase將最近接收到的數據緩存在內存中(in Memstore),在持久化到HDFS之前完成排序,然后再快速的順序寫入HDFS。需要注意的一點是實際的HFile中,不僅僅只是簡單地排序的列數據的列表,詳見Apache HBase I/O – HFile。
除了解決“無序”問題外,Memstore還有一些其他的好處,例如:
- 作為一個內存級緩存,緩存最近增加數據。一種顯而易見的場合是,新插入數據總是比老數據頻繁使用。
- 在持久化寫入之前,在內存中對Rows/Cells可以做某些優化。比如,當數據的version被設為1的時候,對於某些CF的一些數據,Memstore緩存了數個對該Cell的更新,在寫入HFile的時候,僅需要保存一個最新的版本就好了,其他的都可以直接拋棄。
有一點需要特別注意:每一次Memstore的flush,會為每一個CF創建一個新的HFile。 在讀方面相對來說就會簡單一些:HBase首先檢查請求的數據是否在Memstore,不在的話就到HFile中查找,最終返回merged的一個結果給用戶。
HBase Memstore關注要點
迫於以下幾個原因,HBase用戶或者管理員需要關注Memstore並且要熟悉它是如何被使用的:
- Memstore有許多配置可以調整以取得好的性能和避免一些問題。HBase不會根據用戶自己的使用模式來調整這些配置,你需要自己來調整。
- 頻繁的Memstore flush會嚴重影響HBase集群讀性能,並有可能帶來一些額外的負載。
- Memstore flush的方式有可能影響你的HBase schema設計
接下來詳細討論一下這些要點:
Configuring Memstore Flushes
對Memstore Flush來說,主要有兩組配置項:
- 決定Flush觸發時機
- 決定Flush何時觸發並且在Flush時候更新被阻斷(block)
第一組是關於觸發“普通”flush,這類flush發生時,並不影響並行的寫請求。該類型flush的配置項有:
- hbase.hregion.memstore.flush.size
1
2
3
4
5
6
7
8
9
|
<
property
>
<
name
>hbase.hregion.memstore.flush.size</
name
>
<
value
>134217728</
value
>
<
description
>
Memstore will be flushed to disk if size of the memstore
exceeds this number of bytes. Value is checked by a thread that runs
every hbase.server.thread.wakefrequency.
</
description
>
</
property
>
|
- base.regionserver.global.memstore.lowerLimit
1
2
3
4
5
6
7
8
9
10
|
<
property
>
<
name
>hbase.regionserver.global.memstore.lowerLimit</
name
>
<
value
>0.35</
value
>
<
description
>Maximum size of all memstores in a region server before
flushes are forced. Defaults to 35% of heap.
This value equal to hbase.regionserver.global.memstore.upperLimit causes
the minimum possible flushing to occur when updates are blocked due to
memstore limiting.
</
description
>
</
property
>
|
需要注意的是第一個設置是每個Memstore的大小,當你設置該配置項時,你需要考慮一下每台RS承載的region總量。可能一開始你設置的該值比較小,后來隨着region增多,那么就有可能因為第二個設置原因Memstore的flush觸發會變早許多。
第二組設置主要是出於安全考慮:有時候集群的“寫負載”非常高,寫入量一直超過flush的量,這時,我們就希望memstore不要超過一定的安全設置。在這種情況下,寫操作就要被阻止(blocked)一直到memstore恢復到一個“可管理”(manageable)的大小。該類型flush配置項有:
- hbase.regionserver.global.memstore.upperLimit
1
2
3
4
5
6
7
8
9
|
<
property
>
<
name
>hbase.regionserver.global.memstore.upperLimit</
name
>
<
value
>0.4</
value
>
<
description
>Maximum size of all memstores in a region server before new
updates are blocked and flushes are forced. Defaults to 40% of heap.
Updates are blocked and flushes are forced until size of all memstores
in a region server hits hbase.regionserver.global.memstore.lowerLimit.
</
description
>
</
property
>
|
- hbase.hregion.memstore.block.multiplier
1
2
3
4
5
6
7
8
9
10
11
12
|
<
property
>
<
name
>hbase.hregion.memstore.block.multiplier</
name
>
<
value
>2</
value
>
<
description
>
Block updates if memstore has hbase.hregion.block.memstore
time hbase.hregion.flush.size bytes. Useful preventing
runaway memstore during spikes in update traffic. Without an
upper-bound, memstore fills such that when it flushes the
resultant flush files take a long time to compact or split, or
worse, we OOME.
</
description
>
</
property
>
|
某個節點“寫阻塞”對該節點來說影響很大,但是對於整個集群的影響更大。HBase設計為:每個Region僅屬於一個RS但是“寫負載”是均勻分布於整個集群(所有Region上)。有一個如此“慢”的節點,將會使得整個集群都會變慢(最明顯的是反映在速度上)。
提示:嚴重關切Memstore的大小和Memstore Flush Queue的大小。理想情況下,Memstore的大小不應該達到hbase.regionserver.global.memstore.upperLimit的設置,Memstore Flush Queue 的size不能持續增長。
頻繁的Memstore Flushes
要避免“寫阻塞”,貌似讓Flush操作盡量的早於達到觸發“寫操作”的閾值為宜。但是,這將導致頻繁的Flush操作,而由此帶來的后果便是讀性能下降以及額外的負載。
每次的Memstore Flush都會為每個CF創建一個HFile。頻繁的Flush就會創建大量的HFile。這樣HBase在檢索的時候,就不得不讀取大量的HFile,讀性能會受很大影響。
為預防打開過多HFile及避免讀性能惡化,HBase有專門的HFile合並處理(HFile Compaction Process)。HBase會周期性的合並數個小HFile為一個大的HFile。明顯的,有Memstore Flush產生的HFile越多,集群系統就要做更多的合並操作(額外負載)。更糟糕的是:Compaction處理是跟集群上的其他請求並行進行的。當HBase不能夠跟上Compaction的時候(同樣有閾值設置項),會在RS上出現“寫阻塞”。像上面說到的,這是最最不希望的。
提示:嚴重關切RS上Compaction Queue 的size。要在其引起問題前,阻止其持續增大。
想了解更多HFile 創建和合並,可參看 Visualizing HBase Flushes And Compactions。
理想情況下,在不超過hbase.regionserver.global.memstore.upperLimit的情況下,Memstore應該盡可能多的使用內存(配置給Memstore部分的,而不是真個Heap的)。下圖展示了一張“較好”的情況:
“Somewhat”, because we could configure lower limit to be closer to upper, since we barely ever go over it.
說是“較好”,是因為我們可以將“Lower limit”配置的更接近於“Upper limit”,我們幾乎很少有超過它。
Multiple Column Families & Memstore Flush
每次Memstore Flush,會為每個CF都創建一個新的HFile。這樣,不同CF中數據量的不均衡將會導致產生過多HFile:當其中一個CF的Memstore達到閾值flush時,所有其他CF的也會被flush。如上所述,太頻繁的flush以及過多的HFile將會影響集群性能。
提示:很多情況下,一個CF是最好的設計。
HLog (WAL) Size & Memstore Flush
第一張HBase Read/Write path圖中,你可能已經注意到當數據被寫入時會默認先寫入Write-ahead Log(WAL)。WAL中包含了所有已經寫入Memstore但還未Flush到HFile的更改(edits)。在Memstore中數據還沒有持久化,當RegionSever宕掉的時候,可以使用WAL恢復數據。
當WAL(在HBase中成為HLog)變得很大的時候,在恢復的時候就需要很長的時間。因此,對WAL的大小也有一些限制,當達到這些限制的時候,就會觸發Memstore的flush。Memstore flush會使WAL 減少,因為數據持久化之后(寫入到HFile),就沒有必要在WAL中再保存這些修改。有兩個屬性可以配置:
- hbase.regionserver.hlog.blocksize
- hbase.regionserver.maxlogs
你可能已經發現,WAL的最大值由hbase.regionserver.maxlogs * hbase.regionserver.hlog.blocksize (2GB by default)決定。一旦達到這個值,Memstore flush就會被觸發。所以,當你增加Memstore的大小以及調整其他的Memstore的設置項時,你也需要去調整HLog的配置項。否則,WAL的大小限制可能會首先被觸發,因而,你將利用不到其他專門為Memstore而設計的優化。拋開這些不說,通過WAL限制來觸發Memstore的flush並非最佳方式,這樣做可能會會一次flush很多Region,盡管“寫數據”是很好的分布於整個集群,進而很有可能會引發flush“大風暴”。
提示:最好將hbase.regionserver.hlog.blocksize * hbase.regionserver.maxlogs 設置為稍微大於hbase.regionserver.global.memstore.lowerLimit * HBASE_HEAPSIZE.
Compression & Memstore Flush
HBase建議壓縮存儲在HDFS上的數據(比如HFiles)。除了節省硬盤空間,同樣也會顯著地減少硬盤和網絡IO。使用壓縮,當Memstore flush並將數據寫入HDFS時候,數據會被壓縮。壓縮不會減慢多少flush的處理過程,卻會大大減少以上所述問題,例如因為Memstore變大(超過 upper limit)而引起的“寫阻塞”等等。
提示:壓縮庫建議使用Snappy。有關Snappy的介紹及安裝,可分別參考:《Hadoop壓縮-SNAPPY算法》和《Hadoop HBase 配置 安裝 Snappy 終極教程
》