介紹
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等代表真實的時間戳,共有三個列族:contents
、author
、people
;
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的startKey
和endKey
結構如下:
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的網絡開銷;
最後
由於本人水平有限,文中如有錯誤,歡迎指正。