咱們能夠將一個表想象成一個大的映射關係,經過行健、行健+時間戳或行鍵+列(列族:列修飾符),就能夠定位特定數據,Hbase是稀疏存儲數據的,所以某些列能夠是空白的,mysql
Row Keysql |
Time Stampshell |
Column Family:c1數據庫 |
Column Family:c2apache |
||
列緩存 |
值網絡 |
列負載均衡 |
值分佈式 |
||
r1oop |
t7 |
c1:1 |
value1-1/1 |
|
|
t6 |
c1:2 |
value1-1/2 |
|
|
|
t5 |
c1:3 |
value1-1/3 |
|
|
|
t4 |
|
|
c2:1 |
value1-2/1 |
|
t3 |
|
|
c2:2 |
value1-2/2 |
|
t2 |
t2 |
c1:1 |
value2-1/1 |
|
|
t1 |
|
|
c2:1 |
value2-1/1 |
從上表能夠看出,test表有r1和r2兩行數據,而且c1和c2兩個列族,在r1中,列族c1有三條數據,列族c2有兩條數據;在r2中,列族c1有一條數據, 列族c2有一條數據,每一條數據對應的時間戳都用數字來表示,編號越大表示數據越舊,反而表示數據越新。
3:物理視圖
雖然從概念視圖來看每一個表格是由不少行組成的,可是在物理存儲上面,它是按照列來保存的。
Row Key |
Time Stamp |
Column Family:c1 |
|
列 |
值 |
||
r1 |
t7 |
c1:1 |
value1-1/1 |
t6 |
c1:2 |
value1-1/2 |
|
t5 |
c1:3 |
value1-1/3 |
表:HBase數據的物理視圖(1)
Row Key |
Time Stamp |
Column Family:c2 |
|
列 |
值 |
||
r1 |
t4 |
c2:1 |
value1-2/1 |
t3 |
c2:2 |
value1-2/2 |
表:HBase數據的物理視圖(2)
須要注意的是,在概念視圖上面有些列是空白的,這樣的列實際上並不會被存儲,當請求這些空白的單元格時,會返回null值。若是在查詢的時候不
提供時間戳,那麼會返回距離如今最近的那一個版本的數據,由於在存儲的時候,數據會按照時間戳來排序。
這裏咱們用一個學生成績表做爲例子,對HBase的基本操做和基本概念進行講解:
下面是學生的成績表:
name grad course:math course:art
Tom 1 87 97
Jerry 2 100 80
這裏grad對於表來講是一個列,course對於表來講是一個列族,這個列族由兩個列組成:math和art,固然咱們能夠根據咱們的須要在course中創建更多的列族,如computer,physics等相應的列添加入course列族.
有了上面的想法和需求,咱們就能夠在HBase中創建相應的數據表啦!
1, 創建一個表格 scores 具備兩個列族grad 和courese
hbase(main):002:0> create 'scores', 'grade', 'course'
0 row(s) in 4.1610 seconds
2,查看當先HBase中具備哪些表
hbase(main):003:0> list
scores
1 row(s) in 0.0210 seconds
3,查看錶的構造
hbase(main):004:0> describe 'scores'
{NAME => 'scores', IS_ROOT => 'false', IS_META => 'false', FAMILIES => [{NAME => 'course', BLOOMFILTER => 'false', IN_MEMORY => 'false', LENGTH => '2147483647', BLOCKCACHE => 'false', VERSIONS => '3', TTL => '-1', COMPRESSION => 'NONE'}, {NAME => 'grade', BLOOMFILTER => 'false', IN_MEMORY => 'false', LENGTH => '2147483647', BLOCKCACHE => 'false', VERSIONS => '3', TTL => '-1', COMPRESSION => 'NONE'}]}
1 row(s) in 0.0130 seconds
4, 加入一行數據,行名稱爲 Tom 列族grad的列名爲」」 值位1
hbase(main):005:0> put 'scores', 'Tom', 'grade:', '1'
0 row(s) in 0.0070 seconds
5,給Tom這一行的數據的列族添加一列 <math,87>
hbase(main):006:0> put 'scores', 'Tom', 'course:math', '87'
0 row(s) in 0.0040 seconds
6,給Tom這一行的數據的列族添加一列 <art,97>
hbase(main):007:0> put 'scores', 'Tom', 'course:art', '97'
0 row(s) in 0.0030 seconds
7, 加入一行數據,行名稱爲 Jerry 列族grad的列名爲」」 值位2
hbase(main):008:0> put 'scores', 'Jerry', 'grade:', '2'
0 row(s) in 0.0040 seconds
8,給Jerry這一行的數據的列族添加一列 <math,100>
hbase(main):009:0> put 'scores', 'Jerry', 'course:math', '100'
0 row(s) in 0.0030 seconds
9,給Jerry這一行的數據的列族添加一列 <art,80>
hbase(main):010:0> put 'scores', 'Jerry', 'course:art', '80'
0 row(s) in 0.0050 seconds
10,查看scores表中Tom的相關數據
hbase(main):011:0> get 'scores', 'Tom'
COLUMN CELL
course:art timestamp=1224726394286, value=97
course:math timestamp=1224726377027, value=87
grade: timestamp=1224726360727, value=1
3 row(s) in 0.0070 seconds
11,查看scores表中全部數據
hbase(main):012:0> scan 'scores'
ROW COLUMN+CELL
Tom column=course:art, timestamp=1224726394286, value=97
Tom column=course:math, timestamp=1224726377027, value=87
Tom column=grade:, timestamp=1224726360727, value=1
Jerry column=course:art, timestamp=1224726424967, value=80
Jerry column=course:math, timestamp=1224726416145, value=100
Jerry column=grade:, timestamp=1224726404965, value=2
6 row(s) in 0.0410 seconds
12,查看scores表中全部數據courses列族的全部數據
hbase(main):013:0> scan 'scores', ['course:']
ROW COLUMN+CELL
Tom column=course:art, timestamp=1224726394286, value=97
Tom column=course:math, timestamp=1224726377027, value=87
Jerry column=course:art, timestamp=1224726424967, value=80
Jerry column=course:math, timestamp=1224726416145, value=100
4 row(s) in 0.0200 seconds
上面就是HBase的基本shell操做的一個例子,能夠看出,hbase的shell仍是比較簡單易用的,從中也能夠看出HBase shell缺乏不少傳統sql中的一些相似於like等相關操做,固然,HBase做爲BigTable的一個開源實現,而BigTable是做爲 google業務的支持模型,不少sql語句中的一些東西可能還真的不須要.
HBase是一個分佈式的、面向列的數據庫,它和通常關係型數據庫的最大區別是:HBase很適合於存儲非結構化的數據,還有就是它基於列的而不是基於行的模式。
既然HBase是採用KeyValue的列存儲,那Rowkey就是KeyValue的Key了,表示惟一一行。Rowkey也是一段二進制碼流,最大長度爲64KB,內容能夠由使用的用戶自定義。數據加載時,通常也是根據Rowkey的二進制序由小到大進行的。
HBase是根據Rowkey來進行檢索的,系統經過找到某個Rowkey (或者某個 Rowkey 範圍)所在的Region,而後將查詢數據的請求路由到該Region獲取數據。HBase的檢索支持3種方式:
(1) 經過單個Rowkey訪問,即按照某個Rowkey鍵值進行get操做,這樣獲取惟一一條記錄;
(2) 經過Rowkey的range進行scan,即經過設置startRowKey和endRowKey,在這個範圍內進行掃描。這樣能夠按指定的條件獲取一批記錄;
(3) 全表掃描,即直接掃描整張表中全部行記錄。
HBASE按單個Rowkey檢索的效率是很高的,耗時在1毫秒如下,每秒鐘可獲取1000~2000條記錄,不過非key列的查詢很慢。
Table中Family和Qualifier的關係與區別
就像用MySQL同樣,咱們要作的是表設計,MySQL中的表,行,列的在HBase已經有所區別了,在HBase中主要是Table和Family和Qualifier,這三個概念。Table能夠直接理解爲表,而Family和Qualifier其實均可以理解爲列,一個Family下面能夠有多個Qualifier,因此能夠簡單的理解爲,HBase中的列是二級列,也就是說Family是第一級列,Qualifier是第二級列。兩個是父子關係。
測試發現:
在實際應用場景中,對於單column qualifier和多column qualifier兩種狀況,
若是value長度越長,row key長度越短,字段數(column qualifier數)越少,前者和後者在實際傳輸數據量上會相差小些;反之則相差較大。
若是採用多column qualifier的方式存儲,且客戶端採起批量寫入的方式,則能夠根據實際狀況,適當增大客戶端的write buffer大小,以便可以提升客戶端的寫入吞吐量。
從性能的角度談table中family和qualifier的設置
對於傳統關係型數據庫中的一張table,在業務轉換到hbase上建模時,從性能的角度應該如何設置family和qualifier呢?
最極端的,①每一列都設置成一個family,②一個表僅有一個family,全部列都是其中的一個qualifier,那麼有什麼區別呢?
從讀的方面考慮:
family越多,那麼獲取每個cell數據的優點越明顯,由於io和網絡都減小了。
若是隻有一個family,那麼每一次讀都會讀取當前rowkey的全部數據,網絡和io上會有一些損失。
固然若是要獲取的是固定的幾列數據,那麼把這幾列寫到一個family中比分別設置family要更好,由於只需一次請求就能拿回全部數據。
從寫的角度考慮:
首先,內存方面來講,對於一個Region,會爲每個表的每個Family分配一個Store,而每個Store,都會分配一個MemStore,因此更多的family會消耗更多的內存。
其次,從flush和compaction方面說,目前版本的hbase,在flush和compaction都是以region爲單位的,也就是說當一個family達到flush條件時,該region的全部family所屬的memstore都會flush一次,即便memstore中只有不多的數據也會觸發flush而生成小文件。這樣就增長了compaction發生的機率,而compaction也是以region爲單位的,這樣就很容易發生compaction風暴從而下降系統的總體吞吐量。
第三,從split方面考慮,因爲hfile是以family爲單位的,所以對於多個family來講,數據被分散到了更多的hfile中,減少了split發生的機率。這是把雙刃劍。更少的split會致使該region的體積比較大,因爲balance是以region的數目而不是大小爲單位來進行的,所以可能會致使balance失效。而從好的方面來講,更少的split會讓系統提供更加穩定的在線服務。而壞處咱們能夠經過在請求的低谷時間進行人工的split和balance來避免掉。
所以對於寫比較多的系統,若是是離線應該,咱們儘可能只用一個family好了,但若是是在線應用,那仍是應該根據應用的狀況合理地分配family。
首先,不一樣的family是在同一個region下面。而每個family都會分配一個memstore,因此更多的family會消耗更多的內存。
其次,目前版本的hbase,在flush和compaction都是以region爲單位的,也就是說當一個family達到flush條件時,該region的全部family所屬的memstore都會flush一次,即便memstore中只有不多的數據也會觸發flush而生成小文件。這樣就增長了compaction發生的機率,而compaction也是以region爲單位的,這樣就很容易發生compaction風暴從而下降系統的總體吞吐量。
第三,因爲hfile是以family爲單位的,所以對於多個family來講,數據被分散到了更多的hfile中,減少了split發生的機率。這是把雙刃劍。更少的split會致使該region的體積比較大,因爲balance是以region的數目而不是大小爲單位來進行的,所以可能會致使balance失效。而從好的方面來講,更少的split會讓系統提供更加穩定的在線服務。
上述第三點的好處對於在線應用來講是明顯的,而壞處咱們能夠經過在請求的低谷時間進行人工的split和balance來避免掉。
所以對於寫比較多的系統,若是是離線應該,咱們儘可能只用一個family好了,但若是是在線應用,那仍是應該根據應用的狀況合理地分配family。
Rowkey是一個二進制碼流,Rowkey的長度被不少開發者建議說設計在10~100個字節,不過建議是越短越好,不要超過16個字節。
緣由以下:
(1)數據的持久化文件HFile中是按照KeyValue存儲的,若是Rowkey過長好比100個字節,1000萬列數據光Rowkey就要佔用100*1000萬=10億個字節,將近1G數據,這會極大影響HFile的存儲效率;
(2)MemStore將緩存部分數據到內存,若是Rowkey字段過長內存的有效利用率會下降,系統將沒法緩存更多的數據,這會下降檢索效率。所以Rowkey的字節長度越短越好。
(3)目前操做系統是都是64位系統,內存8字節對齊。控制在16個字節,8字節的整數倍利用操做系統的最佳特性。
若是Rowkey是按時間戳的方式遞增,不要將時間放在二進制碼的前面,建議將Rowkey的高位做爲散列字段,由程序循環生成,低位放時間字段,這樣將提升數據均衡分佈在每一個Regionserver實現負載均衡的概率。若是沒有散列字段,首字段直接是時間信息將產生全部新數據都在一個 RegionServer上堆積的熱點現象,這樣在作數據檢索的時候負載將會集中在個別RegionServer,下降查詢效率。
必須在設計上保證其惟一性。
基於Rowkey的上述3個原則,應對不一樣應用場景有不一樣的Rowkey設計建議。
事務數據是帶時間屬性的,建議將時間信息存入到Rowkey中,這有助於提示查詢檢索速度。對於事務數據建議缺省就按天爲數據建表,這樣設計的好處是多方面的。按天分表後,時間信息就能夠去掉日期部分只保留小時分鐘毫秒,這樣4個字節便可搞定。加上散列字段2個字節一共6個字節便可組成惟一 Rowkey。以下圖所示:
事務數據Rowkey設計 | ||||||
第0字節 | 第1字節 | 第2字節 | 第3字節 | 第4字節 | 第5字節 | … |
散列字段 | 時間字段(毫秒) | 擴展字段 | ||||
0~65535(0x0000~0xFFFF) | 0~86399999(0x00000000~0x05265BFF) |
這樣的設計從操做系統內存管理層面沒法節省開銷,由於64位操做系統是必須8字節對齊。可是對於持久化存儲中Rowkey部分能夠節省25%的開銷。也許有人要問爲何不將時間字段以主機字節序保存,這樣它也能夠做爲散列字段了。這是由於時間範圍內的數據仍是儘可能保證連續,相同時間範圍內的數據查找的機率很大,對查詢檢索有好的效果,所以使用獨立的散列字段效果更好,對於某些應用,咱們能夠考慮利用散列字段所有或者部分來存儲某些數據的字段信息,只要保證相同散列值在同一時間(毫秒)惟一。
統計數據也是帶時間屬性的,統計數據最小單位只會到分鐘(到秒預統計就沒意義了)。同時對於統計數據咱們也缺省採用按天數據分表,這樣設計的好處無需多說。按天分表後,時間信息只須要保留小時分鐘,那麼0~1400只需佔用兩個字節便可保存時間信息。因爲統計數據某些維度數量很是龐大,所以須要4個字節做爲序列字段,所以將散列字段同時做爲序列字段使用也是6個字節組成惟一Rowkey。以下圖所示:
統計數據Rowkey設計 | ||||||
第0字節 | 第1字節 | 第2字節 | 第3字節 | 第4字節 | 第5字節 | … |
散列字段(序列字段) | 時間字段(分鐘) | 擴展字段 | ||||
0x00000000~0xFFFFFFFF) | 0~1439(0x0000~0x059F) |
一樣這樣的設計從操做系統內存管理層面沒法節省開銷,由於64位操做系統是必須8字節對齊。可是對於持久化存儲中Rowkey部分能夠節省25%的開銷。預統計數據可能涉及到屢次反覆的重計算要求,需確保做廢的數據能有效刪除,同時不能影響散列的均衡效果,所以要特殊處理。
通用數據採用自增序列做爲惟一主鍵,用戶能夠選擇按天建分表也能夠選擇單表模式。這種模式須要確保同時多個入庫加載模塊運行時散列字段(序列字段)的惟一性。能夠考慮給不一樣的加載模塊賦予惟一因子區別。設計結構以下圖所示。
通用數據Rowkey設計 | ||||
第0字節 | 第1字節 | 第2字節 | 第3字節 | … |
散列字段(序列字段) | 擴展字段(控制在12字節內) | |||
0x00000000~0xFFFFFFFF) | 可由多個用戶字段組成 |
HBase按指定的條件獲取一批記錄時,使用的就是scan方法。 scan方法有如下特色:
(1)scan能夠經過setCaching與setBatch方法提升速度(以空間換時間);
(2)scan能夠經過setStartRow與setEndRow來限定範圍。範圍越小,性能越高。
經過巧妙的RowKey設計使咱們批量獲取記錄集合中的元素挨在一塊兒(應該在同一個Region下),能夠在遍歷結果時得到很好的性能。
(3)scan能夠經過setFilter方法添加過濾器,這也是分頁、多條件查詢的基礎。
在知足長度、三列、惟一原則後,咱們須要考慮如何經過巧妙設計RowKey以利用scan方法的範圍功能,使得獲取一批記錄的查詢速度能提升。下例就描述如何將多個列組合成一個RowKey,使用scan的range來達到較快查詢速度。
例子:
咱們在表中存儲的是文件信息,每一個文件有5個屬性:文件id(long,全局惟一)、建立時間(long)、文件名(String)、分類名(String)、全部者(User)。
咱們能夠輸入的查詢條件:文件建立時間區間(好比從20120901到20120914期間建立的文件),文件名(「中國好聲音」),分類(「綜藝」),全部者(「浙江衛視」)。
假設當前咱們一共有以下文件:
ID | CreateTime | Name | Category | UserID |
1 | 20120902 | 中國好聲音第1期 | 綜藝 | 1 |
2 | 20120904 | 中國好聲音第2期 | 綜藝 | 1 |
3 | 20120906 | 中國好聲音外卡賽 | 綜藝 | 1 |
4 | 20120908 | 中國好聲音第3期 | 綜藝 | 1 |
5 | 20120910 | 中國好聲音第4期 | 綜藝 | 1 |
6 | 20120912 | 中國好聲音選手採訪 | 綜藝花絮 | 2 |
7 | 20120914 | 中國好聲音第5期 | 綜藝 | 1 |
8 | 20120916 | 中國好聲音錄製花絮 | 綜藝花絮 | 2 |
9 | 20120918 | 張瑋獨家專訪 | 花絮 | 3 |
10 | 20120920 | 加多寶涼茶廣告 | 綜藝廣告 | 4 |
這裏UserID應該對應另外一張User表,暫不列出。咱們只需知道UserID的含義:
1表明 浙江衛視; 2表明 好聲音劇組; 3表明 XX微博; 4表明贊助商。調用查詢接口的時候將上述5個條件同時輸入find(20120901,20121001,」中國好聲音」,」綜藝」,」浙江衛視」)。此時咱們應該獲得記錄應該有第一、二、三、四、五、7條。第6條因爲不屬於「浙江衛視」應該不被選中。咱們在設計RowKey時能夠這樣作:採用 UserID + CreateTime + FileID組成RowKey,這樣既能知足多條件查詢,又能有很快的查詢速度。
須要注意如下幾點:
(1)每條記錄的RowKey,每一個字段都須要填充到相同長度。假如預期咱們最多有10萬量級的用戶,則userID應該統一填充至6位,如000001,000002…
(2)結尾添加全局惟一的FileID的用意也是使每一個文件對應的記錄全局惟一。避免當UserID與CreateTime相同時的兩個不一樣文件記錄相互覆蓋。
按照這種RowKey存儲上述文件記錄,在HBase表中是下面的結構:
rowKey(userID 6 + time 8 + fileID 6) name category ….
00000120120902000001
00000120120904000002
00000120120906000003
00000120120908000004
00000120120910000005
00000120120914000007
00000220120912000006
00000220120916000008
00000320120918000009
00000420120920000010
怎樣用這張表?
在創建一個scan對象後,咱們setStartRow(00000120120901),setEndRow(00000120120914)。
這樣,scan時只掃描userID=1的數據,且時間範圍限定在這個指定的時間段內,知足了按用戶以及按時間範圍對結果的篩選。而且因爲記錄集中存儲,性能很好。
而後使用 SingleColumnValueFilter(org.apache.hadoop.hbase.filter.SingleColumnValueFilter),共4個,分別約束name的上下限,與category的上下限。知足按同時按文件名以及分類名的前綴匹配。
(注意:使用SingleColumnValueFilter會影響查詢性能,在真正處理海量數據時會消耗很大的資源,且須要較長的時間)
若是須要分頁還能夠再加一個PageFilter限制返回記錄的個數。
以上,咱們完成了高性能的支持多條件查詢的HBase表結構設計。
第三種防止熱點的方法時反轉固定長度或者數字格式的rowkey。這樣可使得rowkey中常常改變的部分(最沒有意義的部分)放在前面。這樣能夠有效的隨機rowkey,可是犧牲了rowkey的有序性。