hbase二級索引

二級索引與索引Join是多數業務系統要求存儲引擎提供的基本特性,RDBMS早已支持,NOSQL陣營也在摸索着符合自身特色的最佳解決方案。
這篇文章會以HBase作爲對象來討論如何基於Hbase構建二級索引與實現索引join。文末同時會列出目前已知的包括0.19.3版secondary index, ITHbase, Facebook方案和官方Coprocessor的介紹。html

理論目標
在HBase中實現二級索引與索引Join須要考慮三個目標:
1,高性能的範圍檢索。
2,數據的低冗餘(存儲所佔的數據量)。
3,數據的一致性。git

性能與數據冗餘,一致性是相互制約的關係。
若是你實現了高性能地範圍檢索,必然須要靠冗餘索引數據來提高性能,而數據冗餘會致使更新數據時難以實現一致性,特別是分佈式場景下。
若是你不要求高效地範圍檢索,那麼能夠不考慮產生冗餘數據,一致性問題也能夠間接避免,畢竟share nothing是公認的最簡單有效的解決方案。github

理論結合實際,下文會以實例的方式來闡述各個方案是如何選擇偏重點。
這些方案是通過筆者資料查閱和同事的不斷交流後得出的結論,若有錯誤,歡迎指正:apache

 

1,按索引建表
每個索引創建一個表,而後依靠表的row key來實現範圍檢索。row key在HBase中是以B+ tree結構化有序存儲的,因此scan起來會比較效率。
單表以row key存儲索引,column value存儲id值或其餘數據 ,這就是Hbase索引表的結構。服務器

如何Join?
多索引(多表)的join場景中,主要有兩種參考方案:app

1,按索引的種類掃描各自獨立的單索引表,最後將掃描結果merge。
這個方案的特色是簡單,可是若是多個索引掃描結果數據量比較大的話,merge就會遇到瓶頸。框架

好比,如今有一張1億的用戶信息表,建有出生地和年齡兩個索引,我想獲得一個條件是在杭州出生,年齡爲20歲的按用戶id正序排列前10個的用戶列表。
有一種方案是,系統先掃描出生地爲杭州的索引,獲得一個用戶id結果集,這個集合的規模假設是10萬。
而後掃描年齡,規模是5萬,最後merge這些用戶id,去重,排序獲得結果。less

這明顯有問題,如何改良?
保證出生地和年齡的結果是排過序的,能夠減小merge的數據量?但Hbase是按row key排序,value是不能排序的。
變通一下 – 將用戶id冗餘到row key裏?OK,這是一種解決方案了,這個方案的圖示以下:分佈式

merge時提取交集就是所須要的列表,順序是靠索引增長了_id,以字典序保證的。ide

2, 按索引查詢種類創建組合索引。
在方案1的場景中,想象一下,若是單索引數量多達10個會怎麼樣?10個索引,就要merge 10次,性能可想而知。


解決這個問題須要參考RDBMS的組合索引實現。
好比出生地和年齡須要同時查詢,此時若是創建一個出生地和年齡的組合索引,查詢時效率會高出merge不少。
固然,這個索引也須要冗餘用戶id,目的是讓結果天然有序。結構圖示以下:

這個方案的優勢是查詢速度很是快,根據查詢條件,只須要到一張表中檢索便可獲得結果list。缺點是若是有多個索引,就要創建多個與查詢條件一一對應的組合索引,存儲壓力會增大。

在制定Schema設計方案時,設計人員須要充分考慮場景的特色,結合方案一和二來使用。下面是一個簡單的對比:

  單索引 組合索引
檢索性能 優異 優異
存儲 數據不冗餘,節省存儲。 數據冗餘,存儲比較浪費。
事務性 多個索引保證事務性比較困難。 多個索引保證事務性比較困難。
join 性能較差 性能優異
count,sum,avg,etc 符合條件的結果集全表掃描 符合條件的結果集全表掃描

從上表中能夠得知,方案1,2都存在更新時事務性保證比較困難的問題。若是業務系統能夠接受最終一致性的話,事務性會稍微好作一些。不然只能藉助於複雜的分佈式事務,好比JTA,Chubby等技術。
count, sum, avg, max, min等聚合功能,Hbase只能經過硬掃的方式,而且很悲劇,你可能須要作一些hack操做(好比加一個CF,value爲null),不然你在掃描時可能須要往客戶端傳回全部數據。
固然你能夠在這個場景上作一些優化,好比增長狀態表等,但複雜性帶來的風險會更高。
還有一種終極解決方案就是在業務上只提供上一頁和下一頁,這或許是最簡單有效的方案了。

2,單張表多個列族,索引基於列
Hbase提供了列族Column Family特性。
列索引是將Column Family作爲index,多個index值散落到Qualifier,多個column值依據version排列(CF, Qualifer, Version Hbase會保證有序,其中CF和Qualifier正序,Version倒序)。

舉個典型的例子,就是用戶賣了不少商品,這些商品的標題title須要支持like %title%查詢。傳統基於RDMBS就是模糊查詢,基於search engine就是分詞+倒排表。
在HBase中,模糊查詢顯然不知足咱們的要求,接下來只能經過分詞+倒排的方式來存儲。基於CF的倒排表索引結構見下圖:

取數據的時候,只須要根據用戶id(row key)定位到一個row,而後根據分詞定位到qualifier,再經過version的有序list,取top n條記錄便可。不過你們可能會發現個問題,version list的總數量是須要scan全version list才能知道的,這裏須要業務系統自己作一些改進。

如何join?
實現方式同方案1裏的join,多個CF列索引掃描結果後,須要走merge,將多個索引的查詢結果conjunction。

兩個方案的對比彷佛變化就是一個表,一個列,但其實這個方案有個最大的好處,就是解決了事務性的問題,由於全部的索引都是跟單個row key綁定的,咱們知道單個row的更新,在hbase中是保證原子更新的,這就是這個方案的自然優點。當你在考慮單索引時,使用基於列的索引會比單表索 引有更好的適用性。
而組合索引在以列爲存儲粒度的方案裏,也一樣能夠折中實現。理解這種存儲模式的同窗可能已經猜到了,就是基於qualifier。

下表對比了表索引和列索引的優缺點:

  列索引 表索引
檢索性能 檢索數據須要走屢次scan,第一次scan row key,第二次scan qualifier,第三次scan version。 只須要走一次row key的scan便可。
存儲 在沒有組合索引時,存儲較節省 在沒有組合索引時,存儲較節省
事務性 容易保證 保證事務性比較困難
join 性能較差,只有在創建組合條件Qualifier的時候性能會有所改善 性能較差,只有在創建組合表索引的時候性能會有所改善
額外的問題 1,同一個row裏每一個qualifier的version是有大小限制的,不能超過Int的最大值。(別覺得這個值很大,對於海量數據存儲,上億很日常)
2,version的count總數須要額外作處理獲取。
3,單個row數據超過split大小時,會致使不能compaction或compaction內存吃緊,增長風險。
 
count,sum,avg,etc 符合條件的結果集全表掃描 符合條件的結果集全表掃描

雖然列索引缺點這麼多,可是存儲節省帶來的成本優點有時仍是值得咱們去這麼作的,況且它還解決了事務性問題,須要用戶本身去權衡。
值得一提的是,Facebook的消息應用服務器就是基於相似的方案來實現的。

3,ITHBase
方案一中的多表,解決了性能問題,同時帶來了存儲冗餘和數據一致性問題。這兩個問題中,只要解決其中一項,其實也就知足了大多數業務場景。
本方案中,着重關注的是數據一致性。ITHbase的全稱是 Indexed Transactional HBase,從名字中就能看出,事務性是它的重要特性。

ITHBase的事務原理簡介
建一張事務表__GLOBAL_TRX_LOG__,每次開啓事務時,在表中記錄狀態。由於是基於Hbase的HTable,事務表一樣會寫WAL用於恢復,不過這個日誌格式被ITHbase改造過,它稱之爲THLog。
客戶端對多張表更新時,先啓動事務,而後每次PUT,將事務id傳遞給HRegionServer。
ITHbase經過繼承HRegionServer和HReogin類,重寫了大多數操做接口方法,好比put,  update, delete, 用於獲取transactionalId和狀態。
當server收到操做和事務id後,先確認服務端收到,標記當前事務爲待寫入狀態(須要再發起一次PUT)。當全部表的操做完成後,由客戶端統一作commit寫入,作二階段提交。

4,Map-reduce
這個方案沒什麼好說的,存儲節省,也不須要建索引表,只須要靠強大的集羣計算能力便可導出結果。但通常不適合online業務。

5,Coprocessor協處理器
官方0.92.0新版正在開發中的新功能-Coprocessor,支持region級別索引。詳見:
https://issues.apache.org/jira/browse/HBASE-2038

協處理器的機制能夠理解爲,server端添加了一些回調函數。這些回調函數以下:

The Coprocessor interface defines these hooks:

  • preOpen, postOpen: Called before and after the region is reported as online to the master.
  • preFlush, postFlush: Called before and after the memstore is flushed into a new store file.
  • preCompact, postCompact: Called before and after compaction.
  • preSplit, postSplit: Called after the region is split.
  • preClose and postClose: Called before and after the region is reported as closed to the master.

The RegionObserver interface is defines these hooks:

  • preGet, postGet: Called before and after a client makes a Get request.
  • preExists, postExists: Called before and after the client tests for existence using a Get.
  • prePut and postPut: Called before and after the client stores a value.
  • preDelete and postDelete: Called before and after the client deletes a value.
  • preScannerOpen postScannerOpen: Called before and after the client opens a new scanner.
  • preScannerNext, postScannerNext: Called before and after the client asks for the next row on a scanner.
  • preScannerClose, postScannerClose: Called before and after the client closes a scanner.
  • preCheckAndPut, postCheckAndPut: Called before and after the client calls checkAndPut().
  • preCheckAndDelete, postCheckAndDelete: Called before and after the client calls checkAndDelete().

利用這些hooks能夠實現region級二級索引,實現count, sum, avg, max, min等聚合操做而不須要返回全部的數據,詳見 https://issues.apache.org/jira/browse/HBASE-1512

二級索引的原理猜想
由於coprocessor的最終方案還未公佈,就提供的這些hooks來講,二級索引的實現應該是攔截同一個region的put, get, scan, delete等操做。與此同時在同一個reigon裏維護一個索引CF,創建對應的索引表。
基於region的索引表其實有不少侷限性,好比全局排序就很難作。

不過我以爲Coprocessor最大的好處在於其提供了server端的徹底擴展能力,這對於Hbase來講是一個大的躍進。

如何join?

目前還未發佈,不過就瞭解很難從本質上有所突破。解決方案無非就是merge和composite index,一樣事務性是須要解決的難題之一。

業界已經公開的二級索引方案羅列:

0.19.3版Secondary Index

一直關注HBase的同窗,或許知道,早在HBase 0.19.3版發佈時,曾經加入過secondary index的功能,Issue詳見這裏
它的使用例子也很簡單:http://blog.rajeevsharma.in/2009/06/secondary-indexes-in-hbase.html

0.19.3版Secondary Index經過將列值以row key方法存儲,提供索引scan。
但HBase早期的需求主要來自Hadoop。事務的複雜性以及當時發現hadoop-core裏有個很難解決的與ITHBase兼容的問題,導致官方在0.20.0版將其核心代碼移出了hbase-core,改成contrib第三方擴展,Issue詳見這裏

Transactional tableindexed-ITHBase

這個方案就是在0.19.3版被官方剝離出核心的第三方擴展,它的方案上面已經介紹過了。目前支持最新的Hbase 0.90。
是否具有工業強度的穩定性是用戶選擇它的主要障礙。

https://github.com/hbase-trx/hbase-transactional-tableindexed

Facebook方案

facebook採用的是單表多列索引的解決方案,上面已經提到過了。很完美地解決了數據一致性問題,這主要跟他們的使用場景有關。


感興趣的同窗能夠看下這篇blog,本文不做詳述:

blog.huihoo.com/?p=688

HBase官方方案 0.92.0 版開發中 – Coprocessor協處理器

還未發佈,不過hbase官方blog有篇介紹:http://hbaseblog.com/2010/11/30/hbase-coprocessors

Lily Hbase indexing Library

這是一個索引構建,查詢,管理的框架。結構上,就是經過一張indexmeta表管理多張indexdata索引表。
特色是,有一套很是完善的針對int, string, utf-8, decimal等類型的row key排序機制。這個機制在這篇博文中有詳細介紹:

http://brunodumon.wordpress.com/2010/02/17/building-indexes-using-hbase-mapping-strings-numbers-and-dates-onto-bytes/

此外,框架針對join場景(原理=merge),提供了封裝好的conjunction和disjunction工具類。
針對索引構建場景,Hbase indexing library也提供了很方便的接口。

IHbase

IHBase很是相似ITHBase。
IHBase一樣從HBase源碼級別進行了擴展了,從新定義和實現了一些Server,Client端處理邏輯,因此,它是具有強侵入性的。
不幸的是,這個工具在fix完Hbase 0.20.5版兼容bug之後再也沒更新。是否支持0.90以上版本,筆者還何嘗試。
IHBase與ITHBase的一個對比(仁者見仁)
Feature ITHBase IHBase Comment
global ordering yes no IHBase has an index for each region. The flip side of not having global ordering is compatibility with the good old HRegion: results are coming back in row order (and not value order as in ITHBase)
Full table scan? no no THbase does a partial scan on the index table. ITHBase supports specifying start/end rows to limit the number of scanned regions
Multiple Index Usage no yes IHBase can take advantage of multiple indexes in the same scan. IHBase IdxScan object accepts an Expression which allows intersection/unison of several indexed column criteria
Extra disk storage yes no IHBase indexes are created when the region starts/flushes and do not require any extra storage
Extra RAM yes yes IHBase indexes are in memory and hence increase the memory overhead. THBbase indexes increase the number of regions each region server has to support thus costing memory too
Parallel scanning support no yes In ITHBase the index table needs to be consulted and then GETs are issued for each matching row. The behavior of IHBase (as perceived by the client) is no different than a regular scan and hence supports parallel scanning seamlessly. parallel GET can be implemented to speedup THbase scans
原理簡介
在Memstore滿了之後刷磁盤時,IHBase會進行攔截請求併爲這個memstore的數據構建索引。索引另外一個CF的方式存儲在表內。不過只支持region級別(相似coprocessor)
scan的時候,IHBase會結合索引列中的標記,來加速scan。
http://github.com/ykulbak/ihbase 轉:http://kenwublog.com/hbase-secondary-index-and-join
相關文章
相關標籤/搜索