客戶端讀寫數據是先從Zookeeper中獲取RegionServer的元數據信息,好比Region地址信息。在執行數據寫操做時,HBase會先寫MemStore,爲何會寫到MemStore。本篇博客將爲讀者剖析HBase MemStore和Compaction的詳細內容。shell
HBase的內部通訊和數據交互是經過RPC來實現,關於HBase的RPC實現機制下篇博客爲你們分享。客戶端應用程序經過RPC調用HBase服務端的寫入、刪除、讀取等請求,由HBase的Master分配對應的RegionServer進行處理,獲取每一個RegionServer中的Region地址,寫入到HFile文件中,最終進行數據持久化。網絡
在瞭解HBase MemStore以前,咱們能夠先來看看RegionServer的體系結構,其結構圖以下所示: 分佈式
在HBase存儲中,雖然Region是分佈式存儲的最小單元,單並非存儲的最小單元。從圖中可知,事實上Region是由一個或者多個Store構成的,每一個Store保存一個列族(Columns Family)。而每一個Store又由一個MemStore和0到多個StoreFile構成,而StoreFile以HFile的格式最終保存在HDFS上。性能
HBase爲了保證數據的隨機讀取性能,在HFile中存儲RowKey時,按照順序存儲,即有序性。在客戶端的請求到達RegionServer後,HBase爲了保證RowKey的有序性,不會將數據當即寫入到HFile中,而是將每一個執行動做的數據保存在內存中,即MemStore中。MemStore可以很方便的兼容操做的隨機寫入,而且保證全部存儲在內存中的數據是有序的。當MemStore到達閥值時,HBase會觸發Flush機制,將MemStore中的數據Flush到HFile中,這樣便能充分利用HDFS寫入大文件的性能優點,提供數據的寫入性能。學習
整個讀寫流程,以下所示:this
因爲MemStore是存儲放在內存中的,若是RegionServer因爲出現故障或者進程宕掉,會致使內存中的數據丟失。HBase爲了保證數據的完整性,這存儲設計中添加了一個WAL機制。每當HBase有更新操做寫數據到MemStore以前,會寫入到WAL中(Write AHead Log的簡稱)。WAL文件會經過追加和順序寫入,WAL的每一個RegionServer只有一個,同一個RegionServer上的全部Region寫入到同一個WAL文件中。這樣即便某一個RegionServer宕掉,也能夠經過WAL文件,將全部數據按照順序從新加載到內容中。spa
HBase查詢經過RowKey來獲取數據,客戶端應用程序根據對應的RowKey來獲取其對應的Region地址。查找Region的地址信息是經過HBase的元數據表來獲取的,即hbase:meta表所在的Region。經過讀取hbase:meta表能夠找到每一個Region的StartKey、EndKey以及所屬的RegionServer。因爲HBase的RowKey是有序分佈在Region上,因此經過每一個Region的StartKey和EndKey來肯定當前操做的RowKey的Region地址。設計
因爲掃描hbase:meta表會比較耗時,因此客戶端會存儲表的Region地址信息。當請求的Region租約過時時,會從新加載表的Region地址信息。日誌
RegionServer將數據寫入到HFile中不是同步發生的,是須要在MemStore的內存到達閥值時纔會觸發。RegionServer中全部的Region的MemStore的內存佔用量達到總內存的設置佔用量以後,纔會將MemStore中的全部數據寫入到HFile中。同時會記錄以及寫入的數據的順序ID,便於WAL的日誌清理機制定時刪除WAL的無用日誌。code
MemStore大小到達閥值後會Flush到磁盤中,關鍵參數由hbase.hregion.memstore.flush.size屬性配置,默認是128MB。在Flush的時候,不會當即去Flush到磁盤,會有一個檢測的過程。經過MemStoreFlusher類來實現,具體實現代碼以下所示:
private boolean flushRegion(final FlushRegionEntry fqe) { HRegion region = fqe.region; if (!region.getRegionInfo().isMetaRegion() && isTooManyStoreFiles(region)) { if (fqe.isMaximumWait(this.blockingWaitTime)) { LOG.info("Waited " + (EnvironmentEdgeManager.currentTime() - fqe.createTime) + "ms on a compaction to clean up 'too many store files'; waited " + "long enough... proceeding with flush of " + region.getRegionNameAsString()); } else { // If this is first time we've been put off, then emit a log message. if (fqe.getRequeueCount() <= 0) { // Note: We don't impose blockingStoreFiles constraint on meta regions LOG.warn("Region " + region.getRegionNameAsString() + " has too many " + "store files; delaying flush up to " + this.blockingWaitTime + "ms"); if (!this.server.compactSplitThread.requestSplit(region)) { try { this.server.compactSplitThread.requestSystemCompaction( region, Thread.currentThread().getName()); } catch (IOException e) { LOG.error( "Cache flush failed for region " + Bytes.toStringBinary(region.getRegionName()), RemoteExceptionHandler.checkIOException(e)); } } } // Put back on the queue. Have it come back out of the queue // after a delay of this.blockingWaitTime / 100 ms. this.flushQueue.add(fqe.requeue(this.blockingWaitTime / 100)); // Tell a lie, it's not flushed but it's ok return true; } } return flushRegion(region, false, fqe.isForceFlushAllStores()); }
從實現方法來看,若是是MetaRegion,會馬上進行Flush,緣由在於Meta Region優先級高。另外,判斷是否是有太多的StoreFile,這個StoreFile是每次MemStore Flush產生的,每Flush一次就會產生一個StoreFile,因此Store中會有多個StoreFile,即HFile。
另外,在HRegion中也會檢查Flush,即經過checkResources()方法實現。具體實現代碼以下所示:
private void checkResources() throws RegionTooBusyException { // If catalog region, do not impose resource constraints or block updates. if (this.getRegionInfo().isMetaRegion()) return; if (this.memstoreSize.get() > this.blockingMemStoreSize) { blockedRequestsCount.increment(); requestFlush(); throw new RegionTooBusyException("Above memstore limit, " + "regionName=" + (this.getRegionInfo() == null ? "unknown" : this.getRegionInfo().getRegionNameAsString()) + ", server=" + (this.getRegionServerServices() == null ? "unknown" : this.getRegionServerServices().getServerName()) + ", memstoreSize=" + memstoreSize.get() + ", blockingMemStoreSize=" + blockingMemStoreSize); } }
代碼中的memstoreSize表示一個Region中全部MemStore的總大小,而其總大小的結算公式爲:
BlockingMemStoreSize = hbase.hregion.memstore.flush.size * hbase.hregion.memstore.block.multiplier
其中,hbase.hregion.memstore.flush.size默認是128MB,hbase.hregion.memstore.block.multiplier默認是4,也就是說,當整個Region中全部的MemStore的總大小超過128MB * 4 = 512MB時,就會開始出發Flush機制。這樣便避免了內存中數據過多。
隨着HFile文件數量的不斷增長,一次HBase查詢就可能會須要愈來愈多的IO操做,其 時延必然會愈來愈大。於是,HBase設計了Compaction機制,經過執行Compaction來使文件數量基本保持穩定,進而保持讀取的IO次數穩定,那麼延遲時間就不會隨着數據量的增長而增長,而會保持在一個穩定的範圍中。
而後,Compaction操做期間會影響HBase集羣的性能,好比佔用網絡IO,磁盤IO等。所以,Compaction的操做就是短期內,經過消耗網絡IO和磁盤IO等機器資源來換取後續的HBase讀寫性能。
所以,咱們能夠在HBase集羣空閒時段作Compaction操做。HBase集羣資源空閒時段也是咱們清楚,可是Compaction的觸發時段也不能保證了。所以,咱們不能在HBase集羣配置自動模式的Compaction,須要改成手動定時空閒時段執行Compaction。
Compaction觸發的機制有如下幾種:
至於Region分裂,經過hbase.hregion.max.filesize屬性來設置,默認是10GB,通常在HBase生產環境中設置爲30GB。
在作Compaction操做時,若是數據業務量較大,能夠將定時Compaction的頻率設置較短,好比:天天凌晨空閒時段對HBase的全部表作一次Compaction,防止在白天繁忙時段,因爲數據量寫入過大,觸發Compaction操做,佔用HBase集羣網絡IO、磁盤IO等機器資源。
這篇博客就和你們分享到這裏,若是你們在研究學習的過程中有什麼問題,能夠加羣進行討論或發送郵件給我,我會盡我所能爲您解答,與君共勉。