閱讀591 返回首頁    go 技術社區[雲棲]


HBase二級索引

我們會經常談及二級索引,這是對全表數據進行另外一種方式的組織存儲,是針對table級別的。如果要為HBase上的表實現一個強一致性的二級索引,那麼就無法逃避分布式事務,而這一直是用戶最期待的功能。 而即使隻需要保證最終一致性,這個索引也並不好實現,因為你需要額外的表以存儲過程數據,需要解決宕機恢複問題等
撇開分布式事務,我們是否可以考慮對索引的要求進行降級,比如把Region看成是全表下的子表,實現一套Region級別的索引,通過功能上的犧牲以換取實現的簡易及穩定
一般來說,對數據庫建立索引,往往需要單獨的數據結構來存儲索引的數據.在為hbase建立索引時,可以另外建立一張索引表,查詢時先查詢索引表然後用查詢結果查詢數據表.

image

但是對於hbase這種分布式的數據庫來說,最大的問題是解決索引表和數據表的本地性問題,hbase很容易就因為負載均衡,表split等原因把索引表和數據表的數據分布到不同的region server上,比如下圖中,數據表和索引表就出現在了不同的region server上

image

在HBase中實現二級索引與索引Join需要考慮三個目標:
1,高性能的範圍檢索。
2,數據的低冗餘(存儲所占的數據量)。
3,數據的一致性。
1,按索引建表
每一個索引建立一個表,然後依靠表的row key來實現範圍檢索。row key在HBase中是以B+ tree結構化有序存儲的,所以scan起來會比較效率。LSM
單表以row key存儲索引,column value存儲id值或其他數據 ,這就是Hbase索引表的結構。

如何Join?
多索引(多表)的join場景中,主要有兩種參考方案:
1,按索引的種類掃描各自獨立的單索引表,最後將掃描結果merge。
這個方案的特點是簡單,但是如果多個索引掃描結果數據量比較大的話,merge就會遇到瓶頸。

比如,現在有一張1億的用戶信息表,建有出生地和年齡兩個索引,我想得到一個條件是在杭州出生,年齡為20歲的按用戶id正序排列前10個的用戶列表。
有一種方案是,係統先掃描出生地為杭州的索引,得到一個用戶id結果集,這個集合的規模假設是10萬。
然後掃描年齡,規模是5萬,最後merge這些用戶id,去重,排序得到結果。
這明顯有問題,如何改良?
保證出生地和年齡的結果是排過序的,可以減少merge的數據量?但Hbase是按row key排序,value是不能排序的。
變通一下 – 將用戶id冗餘到row key裏?OK,這是一種解決方案了

image
按索引查詢種類建立組合索引。
在方案1的場景中,想象一下,如果單索引數量多達10個會怎麼樣?10個索引,就要merge 10次,性能可想而知。
解決這個問題需要參考RDBMS的組合索引實現。
比如出生地和年齡需要同時查詢,此時如果建立一個出生地和年齡的組合索引,查詢時效率會高出merge很多。
當然,這個索引也需要冗餘用戶id,目的是讓結果自然有序。結構圖示如下:
image
【協處理器的解決思路】
使用HBase的coprocessor。CoProcessor相當於HBase的Observer+hook,目前支持MasterObserver、RegionObserver和WALObserver,基本上對於HBase Table的管理、數據的Put、Delete、Get等操作都可以找到對應的pre和post。這樣如果需要對於某一項Column建立Secondary Indexing,就可以在Put、Delete的時候,將其信息更新到另外一張索引表中。如圖二所示,對於Indexing裏麵的value值是否存儲的問題,可以根據需要進行控製,如果value的空間開銷不大,逆向的檢索又比較頻繁,可以直接存儲在Indexing Table中,反之則避免這種情況。
image

我們要查詢指定店鋪指定客戶購買的訂單,首先有一張訂單詳情表,它以被處理後的訂單id作為rowkey;其次有一張以客戶nick為rowkey的索引表,結構如下: 

rowkey family 
dp_id+buy_nick1 tid1:null tid2:null ... 
dp_id+buy_nick2 tid3:null 

public class TestCoprocessor extends BaseRegionObserver {
public void prePut(final ObserverContext e,
final Put put, final WALEdit edit, final boolean writeToWAL)
throws IOException {
Configuration conf = new Configuration();
HTable table = new HTable(conf, "index_table");
List kv = put.get("data".getBytes(), "name".getBytes());
Iterator kvItor = kv.iterator();
while (kvItor.hasNext()) {
KeyValue tmp = (KeyValue) kvItor.next();
Put indexPut = new Put(tmp.getValue());
indexPut.add("index".getBytes(), tmp.getRow(), Bytes.toBytes(System.currentTimeMillis()));
table.put(indexPut);
}
table.close();
}
}
即繼承BaseRegionObserver類,實現prePut方法,在插入訂單詳情表之前,向索引表插入索引數據。 
索引表的使用 
先在索引表get索引表,獲取tids,然後根據tids查詢訂單詳情表。 
當有多個查詢條件(多張索引表),根據邏輯運算符(and 、or)確定tids。 

使用時注意 

1.索引表是一張普通的hbase表,為安全考慮需要開啟Hlog記錄日誌。 
2.索引表的rowkey最好是不可變量,避免索引表中產生大量的髒數據。 
3.如上例子,column是橫向擴展的(寬表),rowkey設計除了要考慮region均衡,也要考慮column數量,即表不要太寬。建議不超過3位數。 
4.如上代碼,一個put操作其實是先後向兩張表put數據,為保證一致性,需要考慮異常處理,建議異常時重試。

put操作效率不高,如上代碼,每插入一條數據需要創建一個新的索引表連接(可以使用htablepool優化),向索引表插入數據。即耗時是雙倍的,對hbase的集群的壓力也是雙倍的。當索引表有多個時,壓力會更大。 
查詢效率比filter高,毫秒級別,因為都是rowkey的查詢。 
如上是估計的效率情況,需要根據實際業務場景和集群情況而定,最好做預先測試。 

最後更新:2017-11-04 22:33:35

  上一篇:go  找出重傳較高的TCP連接
  下一篇:go  Day2--D1:雲計算概念和體係架構