HBase是如何存儲的

介紹

hbase是面向列族存儲的分佈式數據庫,基於HDFS(本文基於hbase 1.3.x)。

假如在關係型數據庫中有以下數據(第一行是字段名,RowKey字段對應的是主鍵):

RowKey Col1 Col2 Col3
com.cnn.www <html>… CNN John Doc
com.example.www <html>… John Doc

把它映射到HBase表裏是怎麼存儲的呢?往下看。。。

hbase表的邏輯視圖

圖中的t5、t8等代表真實的時間戳,共有三個列族:contentsauthorpeople
rowkey相當於關係型數據庫的主鍵,表內唯一標識一行記錄;同一個rowkey對應的列默認會保存最近的3個版本(寫入時的時間戳就是版本),且按時間倒序排列;查詢的時候,對於一行下的列只會返回最新版本的數據,當然也可以在查詢時指定要查的版本;

真實的數據更像(但不是)下面的json,字段值爲空的是不會佔用空間的:

{
  "com.cnn.www": {
    contents: {
      t6: contents:html: "<html>..."
      t5: contents:html: "<html>..."
      t3: contents:html: "<html>..."
    }
    anchor: {
      t9: anchor:cnnsi.com = "CNN"
      t8: anchor:my.look.ca = "CNN.com"
    }
    people: {}
  }
  "com.example.www": {
    contents: {
      t5: contents:html: "<html>..."
    }
    anchor: {}
    people: {
      t5: people:author: "John Doe"
    }
  }
}

rowkey是按照字典順序排列的,因此可以通過設計rowkey進行遍歷;
字典序對int排序的結果是1,10,100,11,12,13,14,15,16,17,18,19,2,20,21,…,要保持整形的自然序,rowkey必須用0作左填充。(怎麼做到有序?見下文)

hbase表的物理視圖

列族author

列族contents

再看一張更直觀的圖:


hbase物理存儲結構:
Table                      (hbase表)
    Region                 (組成表的region)
        Store               (一個region裏一個列族對應一個Store)
            MemStore     (每個Store裏都有一個MemStore)
            StoreFile      (一個Store裏會有多個StoreFile,在hdfs上叫HFile)
                Block       (一個StoreFile裏有多個Block)

hbase是在hdfs上存儲的,在hdfs上的真實存儲目錄結構:

/hbase
    /data
        /<Namespace>
            /<Table>
                /<Region>
                    /<ColumnFamily>
                        /<StoreFile>

一張hbase表最初只有一個region,如果表的數據量很少,那麼很有可能所有的數據都在一個region裏,隨着數據量增大,單個region會逐漸分裂(超過某個閾值會觸發split,有點類似於細胞分裂),由HMaster做負載均衡;一張表分成多個region,一個RegionServer上往往有多個Region,像下圖這樣:

hbase如何定位數據

Hbase的讀操作大致分爲兩種:
1、通過rowkey get出一條;
2、通過scan操作來遍歷(rowkey是有序的,所以遍歷很高效)

那麼給定一個rowkey如何快速查找到該條記錄呢?
Hbase有個.meta.表,記錄了每個region的startKeyendKey
結構如下:
Key:[table],[region start key],[region id]

Values:
info:regioninfo (serialized HRegionInfo instance for this region)
info:server (server:port of the RegionServer containing this region)
info:serverstartcode (start-time of the RegionServer process containing this region)

第一次查詢時,先從zookeeper上拿到ROOT .META.(也就是.META.表的第一個region,這個region不會split)的位置,.META表的其他region記錄了其他表的region的元數據,客戶端把要訪問的數據對應的region的位置信息和.META.表的位置緩存在本地;如果下一次要查詢的rowkey不在這個region,則會重新查詢.META.表,然後繼續緩存region的位置信息,那麼隨着查詢越來越多,客戶端緩存的region的位置也就越來越多,所以這時候就幾乎沒必要查.META.表了,除非某region被移動;

MemStore Flush

Hbase寫入數據時是先寫到MemStore,當MemStore累積足夠的數據時,整個有序的數據集合都會被寫入(flush)到hdfs中一個新的HFile中,這個寫入是順序寫入,效率高。如果這時候讀取數據,hbase把查MemStore、HFile,並把兩者進行合併(因爲有些數據還沒有flush到HFile)。

rowkey如何有序

hbase表的region會按照RowKey的字典順序排列,因爲region最初只有一個,startKey、endKey都是空的,隨着數據量增大分裂爲兩個,一個只有endKey,另一個只有startKey,然後數據量增大會繼續分裂,所以region之間是有序的;HFile內部的數據記錄也是有序的,因爲數據剛寫入時是放在MemStore中,在MemStore保持有序,隨後寫入HFile中也是順序寫入的,隨着HFile越來越多會有一個負責壓縮的線程(關於壓縮的更多細節不在本文介紹範圍內)將一堆小的HFile壓縮着仍然有序的大的HFile。

說了這麼多廢話,那麼hbase到底是如何存儲的呢?

Hbase的數據是放在HFile裏的,上文說到HFile裏有很多的Block,Block裏又有很多KeyValue,KeyValue裏有什麼?
舉個例子:假如進行兩次PUT操作

Put #1: rowkey=row1, cf:attr1=value1
Put #2: rowkey=row1, cf:attr2=value2

Put #1產生的KeyValue如下:

rowlength -----------→ 4
row -----------------→ row1
columnfamilylength --→ 2
columnfamily --------→ cf
columnqualifier -----→ attr1
timestamp -----------→ timestamp
keytype -------------→ Put

Put #2產生的KeyValue如下:

rowlength -----------→ 4
row -----------------→ row1
columnfamilylength --→ 2
columnfamily --------→ cf
columnqualifier -----→ attr2
timestamp -----------→ timestamp
keytype -------------→ Put

具體HFile裏除了Block還有其他內容,如下圖:


hbase架構

Hbase整體有三個組件構成:
1、 HMaster節點:管理RegionServer,並負責負載均衡;管理和分配Region;接受增刪改操作(不包含查);管理namespace和hbase表的元數據;
2、 HRegionServer節點:接受讀操作;讀寫hdfs;region分裂(split)
3、 ZooKeeper集羣:存放hbase集羣的元數據;實現HMaster的故障轉移、active選舉;

從這張圖可以看出namenode、HMaster都有從節點,通過zookeeper協調,regionserver往往也是datanode,減少讀寫hdfs的網絡開銷;

最後

由於本人水平有限,文中如有錯誤,歡迎指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章