Habse中Rowkey的設計原則——通俗易懂篇
Hbase的Rowkey設計原則
一、Hbase介紹
HBase -> Hadoop Database,HBase是Apache的Hadoop專案的子專案。HBase不同於一般的關係資料庫,它是一個適合於非結構化資料儲存的資料庫。另一個不同的是HBase基於列的而不是基於行的模式,主要用來儲存非結構化和半結構化的鬆散資料(列存NoSQL資料庫)
二、設計原則
2.1Rowkey長度原則
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.2Rowkey雜湊原則
如果Rowkey是按時間戳的方式遞增,不要將時間放在二進位制碼的前面,建議將Rowkey的高位作為雜湊欄位,由程式迴圈生成,低位放時間欄位,這樣將提高資料均衡分佈在每個Regionserver實現負載均衡的機率。如果沒有雜湊欄位,首欄位直接是時間資訊將產生所有新資料都在一個 RegionServer上堆積的熱點現象,這樣在做資料檢索的時候負載將會集中在個別RegionServer,降低查詢效率。
2.3Rowkey唯一原則
必須在設計Rowkey上保證其唯一性。
2.4 訪問hbase table中的行,只有三種方式:
1 通過單個row key訪問
2 通過row key的range
3 全表掃描
Hbase API文件:http://hbase.apache.org/apidocs/index.html?overview-summary.html
HBase的查詢實現只提供兩種方式:
1、按指定RowKey獲取唯一一條記錄,get方法(org.apache.hadoop.hbase.client.Get)
2、按指定的條件獲取一批記錄,scan方法(org.apache.hadoop.hbase.client.Scan)
實現條件查詢功能使用的就是 scan方式,scan在使用時有以下幾點值得注意:
1、 scan可以通過setCaching與setBatch方法提高速度(以空間換時間);
2、 scan可以通過setStartRow與setEndRow來限定範圍。範圍越小,效能越高。
通過巧妙的RowKey設計使我們批量獲取記錄集合中的元素挨在一起(應該在同一個 Region下),可以在遍歷結果時獲得很好的效能。
3、 scan可以通過setFilter方法新增過濾器,這也是分頁、多條件查詢的基礎。
三、 應用場景
3.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%的開銷。也許有人要問為什麼不將時間欄位以主機位元組序儲存,這樣它也可以作為雜湊欄位了。這是因為時間範圍內的資料還是儘量保證連續,相同時間範圍內的資料查詢的概率很大,對查詢檢索有好的效果,因此使用獨立的雜湊欄位效果更好,對於某些應用,我們可以考慮利用雜湊欄位全部或者部分來儲存某些資料的欄位資訊,只要保證相同雜湊值在同一時間(毫秒)唯一。
針對統計資料的Rowkey設計
統計資料也是帶時間屬性的,統計資料最小單位只會到分鐘(到秒預統計就沒意義了)。同時對於統計資料我們也預設採用按天資料分表,這樣設計的好處無需多說。按天分表後,時間資訊只需要保留小時分鐘,那麼0~1400只需佔用兩個位元組即可儲存時間資訊。由於統計資料某些維度數量非常龐大,因此需要4個位元組作為序列欄位,因此將雜湊欄位同時作為序列欄位使用也是6個位元組組成唯一Rowkey。如下圖所示:
統計資料Rowkey設計 |
||||||
第0位元組 |
第1位元組 |
第2位元組 |
第3位元組 |
第4位元組 |
第5位元組 |
… |
雜湊欄位(序列欄位) |
時間欄位(分鐘) |
擴充套件欄位 |
||||
0x00000000~0xFFFFFFFF) |
0~1439(0x0000~0x059F) |
同樣這樣的設計從作業系統記憶體管理層面無法節省開銷,因為64位作業系統是必須8位元組對齊。但是對於持久化儲存中Rowkey部分可以節省25%的開銷。預統計資料可能涉及到多次反覆的重計算要求,需確保作廢的資料能有效刪除,同時不能影響雜湊的均衡效果,因此要特殊處理。
針對通用資料的Rowkey設計
通用資料採用自增序列作為唯一主鍵,使用者可以選擇按天建分表也可以選擇單表模式。這種模式需要確保同時多個入庫載入模組執行時雜湊欄位(序列欄位)的唯一性。可以考慮給不同的載入模組賦予唯一因子區別。設計結構如下圖所示。
通用資料Rowkey設計 |
||||
第0位元組 |
第1位元組 |
第2位元組 |
第3位元組 |
… |
雜湊欄位(序列欄位) |
擴充套件欄位(控制在12位元組內) |
|||
0x00000000~0xFFFFFFFF) |
可由多個使用者欄位組成 |
支援多條件查詢的RowKey設計
下面舉個形象的例子:
我們在表中儲存的是檔案資訊,每個檔案有5個屬性:檔案id(long,全域性唯一)、建立時間(long)、檔名(String)、分類名(String)、所有者(User)。
我們可以輸入的查詢條件:檔案建立時間區間(比如從20120901到20120914期間建立的檔案),檔名(“快樂大本營”),分類(“綜藝”),所有者(“浙江衛視”)。
假設當前我們一共有如下檔案:
內容列表 ID CreateTime Name Category UserID 1 2 3 4 5 6 7 8 9 10
20120902 |
快樂大本營第1期 |
綜藝 |
1 |
20120904 |
快樂大本營第2期 |
綜藝 |
1 |
20120906 |
快樂大本營番外 |
綜藝 |
1 |
20120908 |
快樂大本營第3期 |
綜藝 |
1 |
20120910 |
快樂大本營第4期 |
綜藝 |
1 |
20120912 |
快樂大本營嘉賓採訪 |
綜藝花絮 |
2 |
20120914 |
快樂大本營第5期 |
綜藝 |
1 |
20120916 |
快樂大本營錄製花絮 |
綜藝花絮 |
2 |
20120918 |
王祖藍獨家專訪 |
花絮 |
3 |
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的字典順序排序的,這種設計優化了scan操作,可以將相關的行以及會被一起讀取的行存取在臨近位置,便於scan。然而糟糕的rowkey設計是熱點的源頭。熱點發生在大量的client直接訪問叢集的一個或極少數個節點(訪問可能是讀,寫或者其他操作)。大量訪問會使熱點region所在的單個機器超出自身承受能力,引起效能下降甚至region不可用,這也會影響同一個RegionServer上的其他region,由於主機無法服務其他region的請求。設計良好的資料訪問模式以使叢集被充分,均衡的利用。
為了避免寫熱點,設計rowkey使得不同行在同一個region,但是在更多資料情況下,資料應該被寫入叢集的多個region,而不是一個。
下面是一些常見的避免熱點的方法以及它們的優缺點:
1. 加鹽
這裡所說的加鹽不是密碼學中的加鹽,而是在rowkey的前面增加隨機數,具體就是給rowkey分配一個隨機字首以使得它和之前的rowkey的開頭不同。分配的字首種類數量應該和你想使用資料分散到不同的region的數量一致。加鹽之後的rowkey就會根據隨機生成的字首分散到各個region上,以避免熱點。
2. 雜湊
雜湊會使同一行永遠用一個字首加鹽。雜湊也可以使負載分散到整個叢集,但是讀卻是可以預測的。使用確定的雜湊可以讓客戶端重構完整的rowkey,可以使用get操作準確獲取某一個行資料
3. 反轉
第三種防止熱點的方法時反轉固定長度或者數字格式的rowkey。這樣可以使得rowkey中經常改變的部分(最沒有意義的部分)放在前面。這樣可以有效的隨機rowkey,但是犧牲了rowkey的有序性。
3.1 反轉rowkey的例子
以手機號為rowkey,可以將手機號反轉後的字串作為rowkey,這樣的就避免了以手機號那樣比較固定開頭導致熱點問題
3.2 時間戳反轉
一個常見的資料處理問題是快速獲取資料的最近版本,使用反轉的時間戳作為rowkey的一部分對這個問題十分有用,可以用Long.Max_Value - timestamp追加到key的末尾,例如[key][reverse_timestamp],[key]的最新值可以通過scan [key]獲得[key]的第一條記錄,因為HBase中rowkey是有序的,第一條記錄是最後錄入的資料。
比如需要儲存一個使用者的操作記錄,按照操作時間倒序排序,在設計rowkey的時候,可以這樣設計
[userId反轉][Long.Max_Value - timestamp],在查詢使用者的所有操作記錄資料的時候,直接指定反轉後的userId,startRow是[userId反轉][000000000000],stopRow是[userId反轉][Long.Max_Value - timestamp]
如果需要查詢某段時間的操作記錄,startRow是[user反轉][Long.Max_Value - 起始時間],stopRow是[userId反轉][Long.Max_Value - 結束時間]
3.3 儘量減少行和列的大小
在HBase中,value永遠和它的key一起傳輸的。當具體的值在系統間傳輸時,它的rowkey,列名,時間戳也會一起傳輸。如果你的rowkey和列名很大,HBase storefiles中的索引(有助於隨機訪問)會佔據HBase分配的大量記憶體,因為具體的值和它的key很大。可以增加block大小使得storefiles索引再更大的時間間隔增加,或者修改表的模式以減小rowkey和列名的大小。壓縮也有助於更大的索引。