介紹
hbase是面向列族存儲的分布式數(shù)據(jù)庫,基于HDFS(本文基于hbase 1.3.x)。
假如在關(guān)系型數(shù)據(jù)庫中有以下數(shù)據(jù)(第一行是字段名,RowKey字段對應(yīng)的是主鍵):
| 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相當于關(guān)系型數(shù)據(jù)庫的主鍵,表內(nèi)唯一標識一行記錄;同一個rowkey對應(yīng)的列默認會保存最近的3個版本(寫入時的時間戳就是版本),且按時間倒序排列;查詢的時候,對于一行下的列只會返回最新版本的數(shù)據(jù),當然也可以在查詢時指定要查的版本;
真實的數(shù)據(jù)更像(但不是)下面的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是按照字典順序排列的,因此可以通過設(shè)計rowkey進行遍歷;
字典序?qū)nt排序的結(jié)果是1,10,100,11,12,13,14,15,16,17,18,19,2,20,21,…,要保持整形的自然序,rowkey必須用0作左填充。(怎么做到有序?見下文)
hbase表的物理視圖
列族author:

列族contents:

再看一張更直觀的圖:

hbase物理存儲結(jié)構(gòu):
Table (hbase表)
Region (組成表的region)
Store (一個region里一個列族對應(yīng)一個Store)
MemStore (每個Store里都有一個MemStore)
StoreFile (一個Store里會有多個StoreFile,在hdfs上叫HFile)
Block (一個StoreFile里有多個Block)
hbase是在hdfs上存儲的,在hdfs上的真實存儲目錄結(jié)構(gòu):
/hbase
/data
/<Namespace>
/<Table>
/<Region>
/<ColumnFamily>
/<StoreFile>
一張hbase表最初只有一個region,如果表的數(shù)據(jù)量很少,那么很有可能所有的數(shù)據(jù)都在一個region里,隨著數(shù)據(jù)量增大,單個region會逐漸分裂(超過某個閾值會觸發(fā)split,有點類似于細胞分裂),由HMaster做負載均衡;一張表分成多個region,一個RegionServer上往往有多個Region,像下圖這樣:

hbase如何定位數(shù)據(jù)
Hbase的讀操作大致分為兩種:
1、通過rowkey get出一條;
2、通過scan操作來遍歷(rowkey是有序的,所以遍歷很高效)
那么給定一個rowkey如何快速查找到該條記錄呢?
Hbase有個.meta.表,記錄了每個region的startKey和endKey
結(jié)構(gòu)如下:
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的元數(shù)據(jù),客戶端把要訪問的數(shù)據(jù)對應(yīng)的region的位置信息和.META.表的位置緩存在本地;如果下一次要查詢的rowkey不在這個region,則會重新查詢.META.表,然后繼續(xù)緩存region的位置信息,那么隨著查詢越來越多,客戶端緩存的region的位置也就越來越多,所以這時候就幾乎沒必要查.META.表了,除非某region被移動;

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

rowkey如何有序

hbase表的region會按照RowKey的字典順序排列,因為region最初只有一個,startKey、endKey都是空的,隨著數(shù)據(jù)量增大分裂為兩個,一個只有endKey,另一個只有startKey,然后數(shù)據(jù)量增大會繼續(xù)分裂,所以region之間是有序的;HFile內(nèi)部的數(shù)據(jù)記錄也是有序的,因為數(shù)據(jù)剛寫入時是放在MemStore中,在MemStore保持有序,隨后寫入HFile中也是順序?qū)懭氲模S著HFile越來越多會有一個負責壓縮的線程(關(guān)于壓縮的更多細節(jié)不在本文介紹范圍內(nèi))將一堆小的HFile壓縮著仍然有序的大的HFile。
說了這么多廢話,那么hbase到底是如何存儲的呢?
Hbase的數(shù)據(jù)是放在HFile里的,上文說到HFile里有很多的Block,Block里又有很多KeyValue,KeyValue里有什么?
舉個例子:假如進行兩次PUT操作
Put #1: rowkey=row1, cf:attr1=value1
Put #2: rowkey=row1, cf:attr2=value2
Put #1產(chǎn)生的KeyValue如下:
rowlength -----------→ 4
row -----------------→ row1
columnfamilylength --→ 2
columnfamily --------→ cf
columnqualifier -----→ attr1
timestamp -----------→ timestamp
keytype -------------→ Put
Put #2產(chǎn)生的KeyValue如下:
rowlength -----------→ 4
row -----------------→ row1
columnfamilylength --→ 2
columnfamily --------→ cf
columnqualifier -----→ attr2
timestamp -----------→ timestamp
keytype -------------→ Put
具體HFile里除了Block還有其他內(nèi)容,如下圖:


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

從這張圖可以看出namenode、HMaster都有從節(jié)點,通過zookeeper協(xié)調(diào),regionserver往往也是datanode,減少讀寫hdfs的網(wǎng)絡(luò)開銷;
最后
由于本人水平有限,文中如有錯誤,歡迎指正。