標籤:
1 概述
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列的查詢很慢。
2 HBase的RowKey設計
2.1 設計原則
2.1.1 Rowkey長度原則
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位元組的整數倍利用作業系統的最佳特性。
回到頂部
2.1.2 Rowkey散列原則
如果Rowkey是按時間戳記的方式遞增,不要將時間放在二進位碼的前面,建議將Rowkey的高位作為散欄欄位,由程式迴圈產生,低位放時間欄位,這樣將提高資料均衡分布在每個Regionserver實現負載平衡的幾率。如果沒有散欄欄位,首欄位直接是時間資訊將產生所有新資料都在一個 RegionServer上堆積的熱點現象,這樣在做資料檢索的時候負載將會集中在個別RegionServer,降低查詢效率。
回到頂部
2.1.3 Rowkey唯一原則
必須在設計上保證其唯一性。
2.2 應用情境
基於Rowkey的上述3個原則,應對不同應用情境有不同的Rowkey設計建議。
回到頂部
2.2.1 針對交易資料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%的開銷。也許有人要問為什麼不將時間欄位以主機位元組序儲存,這樣它也可以作為散欄欄位了。這是因為時間範圍內的資料還是盡量保證連續,相同時間範圍內的資料尋找的機率很大,對查詢檢索有好的效果,因此使用獨立的散欄欄位效果更好,對於某些應用,我們可以考慮利用散欄欄位全部或者部分來儲存某些資料的欄位資訊,只要保證相同散列值在同一時間(毫秒)唯一。
回到頂部
2.2.2 針對統計資料的Rowkey設計
統計資料也是帶時間屬性的,統計資料最小單位只會到分鐘(到秒預統計就沒意義了)。同時對於統計資料我們也預設採用按天資料分表,這樣設計的好處無需多說。按天分表後,時間資訊只需要保留小時分鐘,那麼0~1400隻需佔用兩個位元組即可儲存時間資訊。由於統計資料某些維度數量非常龐大,因此需要4個位元組作為序欄欄位,因此將散欄欄位同時作為序欄欄位使用也是6個位元組組成唯一Rowkey。如所示:
統計資料Rowkey設計 |
第0位元組 |
第1位元組 |
第2位元組 |
第3位元組 |
第4位元組 |
第5位元組 |
… |
散欄欄位(序欄欄位) |
時間欄位(分鐘) |
擴充欄位 |
0x00000000~0xFFFFFFFF) |
0~1439(0x0000~0x059F) |
|
同樣這樣的設計從作業系統記憶體管理層面無法節省開銷,因為64位作業系統是必須8位元組對齊。但是對於持久化儲存中Rowkey部分可以節省25%的開銷。預統計資料可能涉及到多次反覆的重計算要求,需確保作廢的資料能有效刪除,同時不能影響散列的均衡效果,因此要特殊處理。
回到頂部
2.2.3 針對通用資料的Rowkey設計
通用資料採用自增序列作為唯一主鍵,使用者可以選擇按天建分表也可以選擇單表模式。這種模式需要確保同時多個入庫載入模組運行時散欄欄位(序欄欄位)的唯一性。可以考慮給不同的載入模組賦予唯一因子區別。設計結構如所示。
通用資料Rowkey設計 |
第0位元組 |
第1位元組 |
第2位元組 |
第3位元組 |
… |
散欄欄位(序欄欄位) |
擴充欄位(控制在12位元組內) |
0x00000000~0xFFFFFFFF) |
可由多個使用者欄位組成 |
回到頂部
2.2.4 支援多條件查詢的RowKey設計
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,”中國好聲音”,”綜藝”,”浙江衛視”)。此時我們應該得到記錄應該有第1、2、3、4、5、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表結構設計。
大資料效能調優之HBase的RowKey設計