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)的理解:
  HBase緩存不像對傳統緩存理解的一樣讀了內存如果有數據就不再讀磁盤,因為它是以時間戳進行版本控制的,所以不能只讀內存。
  HBase讀數據無論如何都會掃描HDFS磁盤,只是在 BlockCache 中存在的數據不再讀取,BlockCache 只是提高了讀磁盤的效率。如:磁盤中有數據A和B,BlockCache 中有數據A,則掃描磁盤時只讀取B,不再讀取A。
  所以 HBase 是寫比讀快(讀不僅掃磁盤,還要合並選取數據)。

(4)StoreFile合並(Compaction)

  由於memstore每次刷寫都會生成一個新的HFile,且同一個字段的不同版本(timestamp)和不同類型(Put/Delete)有可能會分布在不同的 HFile 中,因此查詢時需要遍歷所有的 HFile。
  為了減少 HFile 的個數,以及清理掉過期和刪除的數據,會進行 StoreFile Compaction。Compaction 分為兩種,分別是 Minor Compaction 和 Major Compaction。
                     

  Minor Compaction 是指選取一些小的、相鄰的 HFile 將他們合並成一個更大的 HFile。默認情況下,Minor Compaction 會刪除所合並 HFile 中的 TTL 過期數據,但是不會刪除手動刪除(也就是 Delete 標記作用的數據)不會被刪除。 

  Major Compaction 是指將一個 Store 中所有的 HFile 合並成一個 HFile,這個過程會清理三類沒有意義的數據:被刪除的數據(打了 Delete 標記的數據)、TTL 過期數據、版本號超過設定版本號的數據。另外,一般情況下,Major Compaction 時間會持續比較長,整個過程會消耗大量系統資源,對上層業務有比較大的影響。因此, 生產環境下通常關閉自動觸發 Major Compaction 功能,改為手動在業務低峰期觸發
 
  Compaction 觸發時機:

  概括的說,HBase 會在三種情況下檢查是否要觸發 Compaction,分別是 MemStore Flush、后台線程周期性檢查、手動觸發。

  • MemStore Flush:可以說 Compaction 的根源就在於Flush,MemStore 達到一定閾值或觸發條件就會執行 Flush 操作,在磁盤上生成 HFile 文件,正是因為 HFile 文件越來越多才需要 Compact。HBase 每次Flush 之后,都會判斷是否要進行 Compaction,一旦滿足 Minor Compaction 或 Major Compaction 的條件便會觸發執行。
  • 后台線程周期性檢查: 后台線程 CompactionChecker 會定期檢查是否需要執行 Compaction,檢查周期為 hbase.server.thread.wakefrequency * hbase.server.compactchecker.interval.multiplier,這里主要考慮的是一段時間內沒有寫入仍然需要做 Compact 檢查。其中參數 hbase.server.thread.wakefrequency 默認值 10000 即 10s,是 HBase 服務端線程喚醒時間間隔,用於 LogRoller、MemStoreFlusher 等的周期性檢查;參數 hbase.server.compactchecker.interval.multiplier 默認值1000,是 Compaction 操作周期性檢查乘數因子,10 * 1000 s 時間上約等於2hrs, 46mins, 40sec。
  • 手動觸發:通過 HBase Shell、Master UI 界面或 HBase API 等任一種方式執行 compact、major_compact等命令,會立即觸發 Compaction。
    Compaction 核心參數:

  • hbase.hstore.compaction.min

    默認值 3,一個 Store 中 HFile 文件數量超過該閾值就會觸發一次 Compaction(Minor Compaction),這里稱該參數為 minFilesToCompact。一般不建議調小,重寫場景下可以調大該參數,比如 5~10 之間,

  注意相應調整下一個參數。老版本參數名稱為 hbase.hstore.compactionthreshold。

  • hbase.hstore.compaction.max

    默認值 10,一次 Minor Compaction 最多合並的 HFile 文件數量,這里稱該參數為 maxFilesToCompact。這個參數也控制着一次壓縮的耗時。一般不建議調整,但如果上一個參數調整了,該參數也應該相應調整,

  一般設為 minFilesToCompact 的 2~3 倍。

  • hbase.regionserver.thread.compaction.throttle

    HBase RegionServer 內部設計了兩個線程池 large compactions 與 small compactions,用來分離處理 Compaction 操作,該參數就是控制一個 Compaction 交由哪一個線程池處理,

  默認值是 2 * maxFilesToCompact * hbase.hregion.memstore.flush.size(默認2*10*128M=2560M即2.5G),建議不調整或稍微調大。

  • hbase.regionserver.thread.compaction.large/small

    默認值 1,表示 large compactions 與 small compactions 線程池的大小。一般建議調整到 2~5,不建議再調太大比如10,否則可能會消費過多的服務端資源造成不良影響。

  • hbase.hstore.blockingStoreFiles

  <!-- 每個store阻塞更新請求的閥值,表示如果當前hstore中文件數大於該值,系統將會強制執行compaction操作進行文件合並, 合並的過程會阻塞整個hstore的寫入,這樣有個好處是避免compaction操作趕不上Hfile文件的生成速率 -->  
    <property>  
        <name>hbase.hstore.blockingStoreFiles</name>  
        <value>10</value>  
        <description>  
            If more than this number of StoreFiles in any one Store  
            (one StoreFile is written per flush of MemStore) then updates are  
            blocked for this HRegion until a compaction is completed, or  
            until hbase.hstore.blockingWaitTime has been exceeded.  
        </description>  
    </property>

    默認值 10,表示一個 Store 中 HFile 文件數量達到該值就會阻塞寫入,等待 Compaction 的完成。一般建議調大點,比如設置為 100,避免出現阻塞更新的情況,阻塞日志如下:

too many store files; delaying flush up to 90000ms

    生產環境建議認真根據實際業務量做好集群規模評估,如果小集群遇到了持續寫入過快的場景,合理擴展集群也非常重要。

(5)數據切分(Split)

        

  默認情況下,每個 Table 起初只有一個 Region,隨着數據的不斷寫入,Region 會自動進行拆分。剛拆分時,兩個子 Region 都位於當前的 Region Server,但處於負載均衡的考慮,HMaster 有可能會將某個 Region 轉移給其他的 Region Server。

  Region Split 時機:
    • 當1個region中的某個Store下所有StoreFile的總大小超過hbase.hregion.max.filesize,該 Region 就會進行拆分(0.94 版本之前)。
    • 當 1 個 region 中 的 某 個 Store 下所有 StoreFile 的 總 大 小 超 過 Min(R^2 * "hbase.hregion.memstore.flush.size",hbase.hregion.max.filesize"),該 Region 就會進行拆分,其中 R 為當前 Region Server 中屬於該 Table 的個數(0.94 版本之后)。
   數據切分會造成數據傾斜(region 大小分布不均),帶來熱點數據問題。所以建表時進行預分區來盡量避免這種問題。

(6)被刪除數據(打了 Delete 標記的數據)的清除時機

  被刪除數據會在兩種操作中被清除:
  Flush:內存數據刷寫到磁盤(MemStore ——> StoreFile),這時候只會清除當前內存(memStore)中被 Delete 標記覆蓋的數據,不會清除 Delete 標記。
  major compact:大合並會刪除三類無效數據:被刪除的數據(打了 Delete 標記的數據)、TTL 過期數據、版本號超過設定版本號的數據。
為什么 Flush 不清除 Delete 標記?
  因為 Flush 只能清除 memStore 中的數據,還要留着 Delete 標記供 major compact 合並時去清除其他 StoreFile 中的數據。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM