HBase MemStore和Compaction剖析

1.概述

  客戶端讀寫數據是先從Zookeeper中獲取RegionServer的元數據信息,好比Region地址信息。在執行數據寫操做時,HBase會先寫MemStore,爲何會寫到MemStore。本篇博客將爲讀者剖析HBase MemStore和Compaction的詳細內容。shell

2.內容

  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上。性能

2.1 寫入流程

  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

 2.2 讀取流程

   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地址信息。日誌

2.3 Flush機制

  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機制。這樣便避免了內存中數據過多。

3. Compaction

  隨着HFile文件數量的不斷增長,一次HBase查詢就可能會須要愈來愈多的IO操做,其 時延必然會愈來愈大。於是,HBase設計了Compaction機制,經過執行Compaction來使文件數量基本保持穩定,進而保持讀取的IO次數穩定,那麼延遲時間就不會隨着數據量的增長而增長,而會保持在一個穩定的範圍中。

  而後,Compaction操做期間會影響HBase集羣的性能,好比佔用網絡IO,磁盤IO等。所以,Compaction的操做就是短期內,經過消耗網絡IO和磁盤IO等機器資源來換取後續的HBase讀寫性能。

  所以,咱們能夠在HBase集羣空閒時段作Compaction操做。HBase集羣資源空閒時段也是咱們清楚,可是Compaction的觸發時段也不能保證了。所以,咱們不能在HBase集羣配置自動模式的Compaction,須要改成手動定時空閒時段執行Compaction。

  Compaction觸發的機制有如下幾種:

  1. 自動觸發,配置hbase.hregion.majorcompaction參數,單位爲毫秒
  2. 手動定時觸發:將hbase.hregion.majorcompaction參數設置爲0,而後定時腳本執行:echo "major_compact tbl_name" | hbase shell
  3. 當選中的文件數量大於等於Store中的文件數量時,就會觸發Compaction操做。由屬性hbase.hstore.compaction.ratio決定。

  至於Region分裂,經過hbase.hregion.max.filesize屬性來設置,默認是10GB,通常在HBase生產環境中設置爲30GB。

4.總結

  在作Compaction操做時,若是數據業務量較大,能夠將定時Compaction的頻率設置較短,好比:天天凌晨空閒時段對HBase的全部表作一次Compaction,防止在白天繁忙時段,因爲數據量寫入過大,觸發Compaction操做,佔用HBase集羣網絡IO、磁盤IO等機器資源。

5.結束語

  這篇博客就和你們分享到這裏,若是你們在研究學習的過程中有什麼問題,能夠加羣進行討論或發送郵件給我,我會盡我所能爲您解答,與君共勉。 

相關文章
相關標籤/搜索