1. 程式人生 > 其它 >HBase底層儲存結構和原理

HBase底層儲存結構和原理

1、資料儲存結構

(1)邏輯結構

  邏輯上是一張表,有行有列,但是物理上是k-v儲存的。

  一個列族包含n個列,在屋裡結構上一個列族就是一個資料夾。一個資料夾中包好多個store檔案。

  rowKey又叫行鍵,它是有序的(字典順序)。  

  來看下它的資料模型:

  •Name Space     名稱空間,類似於關係型資料庫的 DatabBase 概念,每個名稱空間下有多個表。HBase有兩個自帶的名稱空間,分別是 hbase 和 default,hbase 中存放的是 HBase 內建的表,default 表是使用者預設使用的名稱空間。   •Region     類似於關係型資料庫的表概念。不同的是,HBase 定義表時只需要宣告列族即可,不需要宣告具體的列。這意味著,往 HBase 寫入資料時,欄位可以動態、按需指定。   因此,和關係型資料庫相比,HBase 能夠輕鬆應對欄位變更的場景。   •Row     HBase 表中的每行資料都由一個 RowKey 和多個 Column(列)組成,資料是按照 RowKey的字典順序儲存的,並且查詢資料時只能根據 RowKey 進行檢索,所以 RowKey 的設計十分重要。   •Column     HBase 中的每個列都由 Column Family(列族)和 Column Qualifier(列限定符)進行限定,例如 info:name,info:age。建表時,只需指明列族,而列限定符無需預先定義。   •Time Stamp     用於標識資料的不同版本(version),每條資料寫入時,如果不指定時間戳,系統會自動為其加上該欄位,其值為寫入 HBase 的時間。   •Cell     由{rowkey, column Family:column Qualifier, time Stamp} 唯一確定的單元。cell 中的資料是沒有型別的,全部是位元組碼形式存貯。

(2)物理結構

   

  HBase在屋裡上就是種種k-v儲存的,實現在HDFS上隨機寫操作,就是通過timeStamp來進行版本控制實現的。這種儲存方式很好地解決了稀疏性問題,因為空值不會佔據儲存空間,不像MySQL那樣NULL值也要值一定的儲存空間。

2、系統架構

  

架構角色:

  •Region Server     Region Server 為 Region 的管理者,其實現類為 HRegionServer,主要作用如下:       對於資料的操作:get, put, delete;       對於 Region 的操作:splitRegion、compactRegion。   •Master     Master 是所有 Region Server 的管理者,其實現類為 HMaster,主要作用如下:       對於表的操作:create, delete, alter       對於 RegionServer的操作:分配 regions到每個RegionServer,監控每個 RegionServer的狀態,負載均衡和故障轉移。   •Zookeeper     HBase 通過 Zookeeper 來做 Master 的高可用、RegionServer 的監控、元資料的入口以及叢集配置的維護等工作。   •HDFS     HDFS 為 HBase 提供最終的底層資料儲存服務,同時為 HBase 提供高可用的支援。 資料儲存角色:   •StoreFile     儲存實際資料的物理檔案,StoreFile 以 HFile 的形式儲存在 HDFS 上。每個 Store 會有一個或多個 StoreFile(HFile),資料在每個 StoreFile 中都是有序的。   •MemStore     寫快取,由於 HFile 中的資料要求是有序的,所以資料是先儲存在 MemStore 中,排好序後,等到達刷寫時機才會刷寫到 HFile,每次刷寫都會形成一個新的 HFile
。   •WAL     由於資料要經 MemStore 排序後才能刷寫到 HFile,但把資料儲存在記憶體中會有很高的概率導致資料丟失,為了解決這個問題,資料會先寫在一個叫做 Write-Ahead logfile 的檔案中,然後再寫入 MemStore 中。   所以在系統出現故障的時候,資料可以通過這個日誌檔案重建。

3、執行原理

(1)寫資料流程

  

  1)Client 先訪問 zookeeper,獲取 hbase:meta 表位於哪個 Region Server。

  2)訪問對應的 Region Server,獲取 hbase:meta 表,根據讀請求的 namespace:table/rowkey,查詢出目標資料位於哪個 Region Server 中的哪個 Region 中。   並將該 table 的 region 資訊以及 meta 表的位置資訊快取在客戶端的 meta cache,方便下次訪問。   3)與目標 Region Server 進行通訊;   4)將資料順序寫入(追加)到 WAL;   5)將資料寫入對應的 MemStore,資料會在 MemStore 進行排序;   6)向客戶端傳送 ack;   7)等達到 MemStore 的刷寫時機後,將資料刷寫到 HFile。

說明:寫入資料時,邏輯順序是先寫入到WAL(HLog),再寫入memStore,但實際原始碼流程如下:使用事務回滾機制來確保WAL和memStore同步寫入。

  1)先獲取鎖(JUC),保證資料不會寫入成功前資料不會被查詢到

  2)更新時間戳(資料中沒定義時間戳則使用系統時間),叢集中個HRegionServer的系統時間誤差不能超過配置時間,否則叢集啟動不成功

  3)記憶體中構建WAL

  4)將構建的WAL追加到最後寫入的WAL,此時不會將WAL同步到HDFS中的HLog中

  5)將資料寫入到memStore

  6)釋放鎖

  7)同步WAL到HLog,如果同步失敗,會回滾memStore(通過doRollBackMemstore=false控制)

  8)9).........

(2)MemStore刷寫(flush)

  

  第一種觸發條件:(Region級別Flush)

    當某個 memstroe 的大小達到了 hbase.hregion.memstore.flush.size(預設值 128M),其所在 region 的所有 memstore 都會刷寫。

  如果寫入速度太快超過了刷寫速度,當 memstore 的大小達到了hbase.hregion.memstore.flush.size * hbase.hregion.memstore.block.multiplier(預設值 4)大小時,會觸發所有memstore 刷寫並阻塞 Region 所有的寫請求,

  此時寫資料會出現 RegionTooBusyException 異常。

  <!-- 單個region裡memstore的快取大小,超過那麼整個HRegion就會flush,預設128M -->  
    <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>
  <!-- 當一個HRegion上的memstore的大小滿足hbase.hregion.memstore.block.multiplier *   
        hbase.hregion.memstore.flush.size, 這個HRegion會執行flush操作並阻塞對該HRegion的寫入 -->  
    <property>  
        <name>hbase.hregion.memstore.block.multiplier</name> 
        <value>4</value>  
        <description>  
            Block updates if memstore has hbase.hregion.memstore.block.multiplier  
            times hbase.hregion.memstore.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>

  第二種觸發條件:(RegionServer級別Flush)

    當 regionServer 中 memstore 的總大小達到java_heapsize(堆記憶體)* hbase.regionserver.global.memstore.size(預設值 0.4)* hbase.regionserver.global.memstore.size.lower.limit(預設值 0.95),

  region 會按照其所有 memstore 的大小順序(由大到小)依次進行刷寫。直到 regionServer中所有 memstore 的總大小減小到上述值以下。

    當 region server 中 memstore 的總大小達到java_heapsize * hbase.regionserver.global.memstore.size(預設值 0.4)時,會阻止繼續往所有的 memstore 寫資料。
  <!-- regionServer的全域性memstore的大小,超過該大小會觸發flush到磁碟的操作,預設是堆大小的40%,而且regionserver級別的   
        flush會阻塞客戶端讀寫。如果RegionServer的堆記憶體設定為2G,則memstore總記憶體大小為 2 * 0.4 = 0.8G-->  
    <property>  
        <name>hbase.regionserver.global.memstore.size</name>  
        <value></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 (0.4).  
            Updates are blocked and flushes are forced until size of all  
            memstores  
            in a region server hits  
            hbase.regionserver.global.memstore.size.lower.limit.  
            The default value in this configuration has been intentionally left  
            emtpy in order to  
            honor the old hbase.regionserver.global.memstore.upperLimit property if  
            present.  
        </description>  
    </property>  
    <!--可以理解為一個安全的設定,有時候叢集的“寫負載”非常高,寫入量一直超過flush的量,這時,我們就希望memstore不要超過一定的安全設定。   
        在這種情況下,寫操作就要被阻塞一直到memstore恢復到一個“可管理”的大小, 這個大小就是預設值是堆大小 * 0.4 * 0.95,也就是當regionserver級別   
        的flush操作傳送後,會阻塞客戶端寫,一直阻塞到整個regionserver級別的memstore的大小為 堆大小 * 0.4 *0.95為止 -->  
    <property>  
        <name>hbase.regionserver.global.memstore.size.lower.limit</name>  
        <value></value>  
        <description>Maximum size of all memstores in a region server before  
            flushes are forced.  
            Defaults to 95% of hbase.regionserver.global.memstore.size (0.95).  
            A 100% value for this value causes the minimum possible flushing to  
            occur when updates are  
            blocked due to memstore limiting.  
            The default value in this configuration has been intentionally left  
            emtpy in order to  
            honor the old hbase.regionserver.global.memstore.lowerLimit property if  
            present.  
        </description>  
    </property>

  第三種觸發條件:(RegionServer級別Flush)

    定期 hbase.regionserver.optionalcacheflushinterval(預設3600000即一個小時)進行 MemStore 的刷寫,確保 MemStore 不會長時間沒有持久化。為避免所有的 MemStore 在同一時間進行 flush 而導致問題,

  定期的 flush 操作會有一定時間的隨機延時。

  第四種觸發條件:(Region級別Flush)

    當一個 Region 的更新次數達到 hbase.regionserver.flush.per.changes(預設30000000即3千萬)時,也會觸發 MemStore 的刷寫。
  第五種觸發條件:     當一個 RegionServer 的 HLog 即WAL檔案數量達到上限(舊版本可通過引數 hbase.regionserver.maxlogs,新版不再對外開放 ,預設32)時,也會觸發 MemStore 的刷寫,   HBase 會找到最舊的 HLog 檔案對應的 Region 進行刷寫 。    關於相關引數的說明和調整建議:   •hbase.hregion.memstore.flush.size

  預設值 128M,單個 MemStore 大小超過該閾值就會觸發 Flush。如果當前叢集 Flush 比較頻繁,並且記憶體資源比較充裕,建議適當調整為 256M。調大的副作用可能是造成宕機時需要分裂的 HLog 數量變多,從而延長故障恢復時間。

  •hbase.hregion.memstore.block.multiplier

  預設值 4,Region 中所有 MemStore 超過單個 MemStore 大小的倍數達到該引數值時,就會阻塞寫請求並強制 Flush。一般不建議調整,但對於寫入過快且記憶體充裕的場景,為避免寫阻塞,可以適當調整到5~8。

  •hbase.regionserver.global.memstore.size

  預設值 0.4,RegionServer 中所有 MemStore 大小總和最多佔 RegionServer 堆記憶體的 40%。這是寫快取的總比例,可以根據實際場景適當調整,且要與 HBase 讀快取引數 hfile.block.cache.size(預設也是0.4)配合調整。舊版本引數名稱為 hbase.regionserver.global.memstore.upperLimit。

  •hbase.regionserver.global.memstore.size.lower.limit

  預設值 0.95,表示 RegionServer 中所有 MemStore 大小的低水位是 hbase.regionserver.global.memstore.size 的 95%,超過該比例就會強制 Flush。一般不建議調整。舊版本引數名稱為 hbase.regionserver.global.memstore.lowerLimit。

  •hbase.regionserver.optionalcacheflushinterval

  預設值 3600000(即 1 小時),HBase 定期 Flush 所有 MemStore 的時間間隔。一般建議調大,比如 10 小時,因為很多場景下 1 小時 Flush 一次會產生很多小檔案,一方面導致 Flush 比較頻繁,另一方面導致小檔案很多,影響隨機讀效能,因此建議設定較大值。

此外還有一點值得說一下:   官方建議使用一個列族,因為一個列族就對應磁碟中的一個檔案,如果使用多個列族,資料不均勻的情況下,在全域性Flush時會產生較多的小檔案。     如: 列族 CF1 CF2 CF3 100M 1K 5K   全域性Flush時,對於CF2和CF3會生成2個小檔案。但是也可以使用多個列族,儘量保證資料均勻就好。

(3)讀流程

  

  1)Client 先訪問 zookeeper,獲取 hbase:meta 表位於哪個 Region Server。

  2)訪問對應的 Region Server,獲取 hbase:meta 表,根據讀請求的 namespace:table/rowkey,查詢出目標資料位於哪個 Region Server 中的哪個 Region 中。並將該 table 的 region 資訊以

  及 meta 表的位置資訊快取在客戶端的 meta cache,方便下次訪問。   3)與目標 Region Server 進行通訊;   4)分別在 Block Cache(讀快取),MemStore 和 Store File(HFile)中查詢目標資料,並將查到的所有資料進行合併。此處所有資料是指同一條資料的不同版本(time stamp)或者不同的型別(Put/Delete)。   5) 將從檔案中查詢到的資料塊(Block,HFile 資料儲存單元,預設大小為 64KB)快取到Block Cache。   6)將合併後的最終結果返回給客戶端。   這裡說一下對讀快取(BlockCache)的理解: