1. 程式人生 > >HBase的rowkey設計(含例項)

HBase的rowkey設計(含例項)

轉自:http://www.aboutyun.com/thread-7119-1-1.html

對於任何系統的資料設計,我們都想提高效能,達到資源最大化利用,那麼對於hbase我們產生如下問題:

1.hbase rowkey設計如何才能提高效能?
2.hbase rowkey如何設計才能雜湊到不同的節點上?

訪問hbase table中的行,只有三種方式:

1 通過單個row key訪問
2 通過row key的range
3 全表掃描


文中可能涉及到的API:

Hadoop/HDFS:http://hadoop.apache.org/common/docs/current/api/
HBase: 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方法新增過濾器,這也是分頁、多條件查詢的基礎。

-------------------------------------------------------------------------------------------------------------------------------------------------

下面舉個形象的例子:

我們在表中儲存的是檔案資訊,每個檔案有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會影響查詢效能,在真正處理海量資料時會消耗很大的資源,且需要較長的時間。


在後續的博文中我將多舉幾種應用場景下rowKey的,可以滿足簡單條件下海量資料瞬時返回的查詢功能)


如果需要分頁還可以再加一個PageFilter限制返回記錄的個數。


以上,我們完成了高效能的支援多條件查詢的HBase表結構設計。

-------------------------------------------------------------------------------------------------------------------------------------------------
如何雜湊儲存

即時間上連續的資料。這些資料可能來自於某個感測器網路、證券交易或者一個監控系統。它們顯著的特點就是rowkey中含有事件發生時間。帶來的一個問題便是HBase對於row的不均衡分佈,它們被儲存在一個唯一的rowkey區間中,被稱為region,區間的範圍被稱為Start Key和End Key。

對於單調遞增的時間型別資料,很容易被雜湊到同一個Region中,這樣它們會被儲存在同一個伺服器上,從而所有的訪問和更新操作都會集中到這一臺伺服器上,從而在叢集中形成一個hot spot,從而不能將叢集的整體效能發揮出來。

要解決這個問題是非常容易的,只需要將所有的資料雜湊到全部的Region上即可。這是可以做到的,比如,在rowkey前面加上一個非執行緒序列,常常有如下選擇:

Hash雜湊

您可以使用一個Hash字首來保證所有的行被分發到多個Region伺服器上。例如:
byte prefix =
(byte) (Long.hashCode(timestamp) % <number of regionservers>);
byte[] rowkey =
Bytes.add(Bytes.toBytes(prefix), Bytes.toBytes(timestamp);

這個公式可以產生足夠的數字,將資料雜湊到所有的Region伺服器上。當然,公式裡假定了Region伺服器的數目。如果您打算後期擴容您的叢集,那麼您可以把它先設定為叢集的整數倍。生成的rowkey類似下面:

0myrowkey-1,
1myrowkey-2, 2myrowkey-3, 0myrowkey-4, 1myrowkey-5, \
2myrowkey-6, …

當他們將按如下順序被髮送到各個Region伺服器上去:

0myrowkey-1
0myrowkey-4
1myrowkey-2
1myrowkey-5

換句話說,對於0myrowkey-1和0myrowkey-4的更新操作會被髮送到同一個region伺服器上去(假定它們沒有被雜湊到兩個region上去),1myrowkey-2和1myrowkey-5會被髮送到同一臺伺服器上。

這種方式的缺點是,rowkey的範圍必須通過程式碼來控制,同時對資料的訪問,可能要訪問多臺region伺服器。當然,可以通過多個執行緒同時訪問,來實現並行化的資料讀取。這種類似於只有map的MapReduce任務,可以大大增加IO的效能。

案例:Mozilla

Socoroo

Mozilla公司搭建了一個名為Socorro的crash報告系統,用來跟蹤Firefox和Thunderbird的crash記錄,儲存所有的使用者提交的關於程式非正常中止的報告。這些報告被順序訪問,通過Mozilla的開發團隊進行分析,使得它們的應用軟體更加穩定。

這些程式碼是開源的,包含著Python寫的客戶端程式碼。它們使用Thrift直接與HBase叢集進行互動。下面的給出了程式碼中用於Hash時間的部分:
def
merge_scan_with_prefix(self,table,prefix,columns):
“”"
A generator based
iterator that yields totally ordered rows starting with a
given prefix. The
implementation opens up 16 scanners (one for each leading
hex character of
the salt) simultaneously and then yields the next row in
order from the
pool on each iteration.
“”"
iterators = []
next_items_queue =
[]
for salt in
’0123456789abcdef’:
salted_prefix =
“%s%s” % (salt,prefix)
scanner = self.client.scannerOpenWithPrefix(table,
salted_prefix, columns)
iterators.append(salted_scanner_iterable(self.logger,self.client,
self._make_row_nice,salted_prefix,scanner))
# The i below is
so we can advance whichever scanner delivers us the polled
# item.
for i,it in
enumerate(iterators):
try:
next = it.next
next_items_queue.append([next(),i,next])
except
StopIteration:
pass
heapq.heapify(next_items_queue)
while 1:
     try:
              while  1:
                       row_tuple,iter_index,next= s = next_items_queue[0]
                       #tuple[1]
is the actual nice row.
                       yield
row_tuple[1]
                       s[0]
= next()
                       heapq.heapreplace(next_items_queue,s)
     except
StopIteration:
              heapq.heappop(next_items_queue)
     except
IndexError:
              return
這些Python程式碼打開了一定數目的scanner,加上Hash後的字首。這個字首是一個單字元的,共有16個不同的字母。heapq物件將scanner的結果進行全域性排序。



欄位位置交換

在前面提到了Key部分掃描,您可以移動timestamp欄位,將它放在前一個欄位的前面。這種方法通過rowkey的組合來將一個順序遞增的timestamp欄位放在rowkey的第二個位置上。

如果你的rowkey不單單含有一個欄位,您可以交換它們的位置。如果你現在的rowkey只有一個timestamp欄位,您有必要再選出一個欄位放在rowkey中。當然,這也帶來了一個缺點,即您常常只能通過rowkey的範圍查詢來訪問資料,比如timestamp的範圍。

--------------------

https://www.cnblogs.com/cxzdy/p/5118456.