Hbase二級索引

二級索引與索引Join是多數(shù)業(yè)務系統(tǒng)要求存儲引擎提供的基本特性,RDBMS早已支持,NOSQL陣營也在摸索著符合自身特點的最佳解決方案。這篇文章會以Hbase做為對象來討論如何基于Hbase構(gòu)建二級索引與實現(xiàn)索引join。文末同時會列出目前已知的包括0.19.3版secondary index, ITHbase, Facebook方案和官方Coprocessor的介紹。
理論目標在HBase中實現(xiàn)二級索引與索引Join需要考慮三個目標:1,高性能的范圍檢索。2,數(shù)據(jù)的低冗余(存儲所占的數(shù)據(jù)量)。3,數(shù)據(jù)的一致性。
性能與數(shù)據(jù)冗余,一致性是相互制約的關系。如果你實現(xiàn)了高性能地范圍檢索,必然需要靠冗余索引數(shù)據(jù)來提升性能,而數(shù)據(jù)冗余會導致更新數(shù)據(jù)時難以實現(xiàn)一致性,特別是分布式場景下。如果你不要求高效地范圍檢索,那么可以不考慮產(chǎn)生冗余數(shù)據(jù),一致性問題也可以間接避免,畢竟share nothing是公認的最簡單有效的解決方案。
理論結(jié)合實際,下文會以實例的方式來闡述各個方案是如何選擇偏重點。這些方案是經(jīng)過筆者資料查閱和同事的不斷交流后得出的結(jié)論,如有錯誤,歡迎指正:

1,按索引建表每一個索引建立一個表,然后依靠表的row key來實現(xiàn)范圍檢索。row key在HBase中是以B+ tree結(jié)構(gòu)化有序存儲的,所以scan起來會比較效率。單表以row key存儲索引,column value存儲id值或其他數(shù)據(jù) ,這就是Hbase索引表的結(jié)構(gòu)。
如何Join?多索引(多表)的join場景中,主要有兩種參考方案:
1,按索引的種類掃描各自獨立的單索引表,最后將掃描結(jié)果merge。這個方案的特點是簡單,但是如果多個索引掃描結(jié)果數(shù)據(jù)量比較大的話,merge就會遇到瓶頸。
比如,現(xiàn)在有一張1億的用戶信息表,建有出生地和年齡兩個索引,我想得到一個條件是在杭州出生,年齡為20歲的按用戶id正序排列前10個的用戶列表。有一種方案是,系統(tǒng)先掃描出生地為杭州的索引,得到一個用戶id結(jié)果集,這個集合的規(guī)模假設是10萬。然后掃描年齡,規(guī)模是5萬,最后merge這些用戶id,去重,排序得到結(jié)果。
這明顯有問題,如何改良?保證出生地和年齡的結(jié)果是排過序的,可以減少merge的數(shù)據(jù)量?但Hbase是按row key排序,value是不能排序的。變通一下 – 將用戶id冗余到row key里?OK,這是一種解決方案了,這個方案的圖示如下:


merge時提取交集就是所需要的列表,順序是靠索引增加了_id,以字典序保證的。
2, 按索引查詢種類建立組合索引。在方案1的場景中,想象一下,如果單索引數(shù)量多達10個會怎么樣?10個索引,就要merge 10次,性能可想而知。
解決這個問題需要參考RDBMS的組合索引實現(xiàn)。比如出生地和年齡需要同時查詢,此時如果建立一個出生地和年齡的組合索引,查詢時效率會高出merge很多。當然,這個索引也需要冗余用戶id,目的是讓結(jié)果自然有序。結(jié)構(gòu)圖示如下:

這個方案的優(yōu)點是查詢速度非常快,根據(jù)查詢條件,只需要到一張表中檢索即可得到結(jié)果list。缺點是如果有多個索引,就要建立多個與查詢條件一一對應的組合索引,存儲壓力會增大。
在制定Schema設計方案時,設計人員需要充分考慮場景的特點,結(jié)合方案一和二來使用。下面是一個簡單的對比:

單索引
組合索引

檢索性能
優(yōu)異
優(yōu)異

存儲
數(shù)據(jù)不冗余,節(jié)省存儲。
數(shù)據(jù)冗余,存儲比較浪費。

事務性
多個索引保證事務性比較困難。
多個索引保證事務性比較困難。

join
性能較差
性能優(yōu)異

count,sum,avg,etc
符合條件的結(jié)果集全表掃描
符合條件的結(jié)果集全表掃描

從上表中可以得知,方案1,2都存在更新時事務性保證比較困難的問題。如果業(yè)務系統(tǒng)可以接受最終一致性的話,事務性會稍微好做一些。否則只能借助于復雜的分布式事務,比如JTA,Chubby等技術(shù)。count, sum, avg, max, min等聚合功能,Hbase只能通過硬掃的方式,并且很悲劇,你可能需要做一些hack操作(比如加一個CF,value為null),否則你在掃描時可能需要往客戶端傳回所有數(shù)據(jù)。當然你可以在這個場景上做一些優(yōu)化,比如增加狀態(tài)表等,但復雜性帶來的風險會更高。還有一種終極解決方案就是在業(yè)務上只提供上一頁和下一頁,這或許是最簡單有效的方案了。
2,單張表多個列族,索引基于列Hbase提供了列族Column Family特性。列索引是將Column Family做為index,多個index值散落到Qualifier,多個column值依據(jù)version排列(CF, Qualifer, Version Hbase會保證有序,其中CF和Qualifier正序,Version倒序)。
舉個典型的例子,就是用戶賣了很多商品,這些商品的標題title需要支持like %title%查詢。傳統(tǒng)基于RDMBS就是模糊查詢,基于search engine就是分詞+倒排表。在HBase中,模糊查詢顯然不滿足我們的要求,接下來只能通過分詞+倒排的方式來存儲。基于CF的倒排表索引結(jié)構(gòu)見下圖:


取數(shù)據(jù)的時候,只需要根據(jù)用戶id(row key)定位到一個row,然后根據(jù)分詞定位到qualifier,再通過version的有序list,取top n條記錄即可。不過大家可能會發(fā)現(xiàn)個問題,version list的總數(shù)量是需要scan全version list才能知道的,這里需要業(yè)務系統(tǒng)本身做一些改進。
如何join?實現(xiàn)方式同方案1里的join,多個CF列索引掃描結(jié)果后,需要走merge,將多個索引的查詢結(jié)果conjunction。
兩個方案的對比似乎變化就是一個表,一個列,但其實這個方案有個最大的好處,就是解決了事務性的問題,因為所有的索引都是跟單個row key綁定的,我們知道單個row的更新,在hbase中是保證原子更新的,這就是這個方案的天然優(yōu)勢。當你在考慮單索引時,使用基于列的索引會比單表索 引有更好的適用性。而組合索引在以列為存儲粒度的方案里,也同樣可以折中實現(xiàn)。理解這種存儲模式的同學可能已經(jīng)猜到了,就是基于qualifier。
下表對比了表索引和列索引的優(yōu)缺點:

列索引
表索引

檢索性能
檢索數(shù)據(jù)需要走多次scan,第一次scan row key,第二次scan qualifier,第三次scan version。
只需要走一次row key的scan即可。

存儲
在沒有組合索引時,存儲較節(jié)省
在沒有組合索引時,存儲較節(jié)省

事務性
容易保證
保證事務性比較困難

join
性能較差,只有在建立組合條件Qualifier的時候性能會有所改善
性能較差,只有在建立組合表索引的時候性能會有所改善

額外的問題
1,同一個row里每個qualifier的version是有大小限制的,不能超過Int的最大值。(別以為這個值很大,對于海量數(shù)據(jù)存儲,上億很平常)2,version的count總數(shù)需要額外做處理獲取。3,單個row數(shù)據(jù)超過split大小時,會導致不能compaction或compaction內(nèi)存吃緊,增加風險。

count,sum,avg,etc
符合條件的結(jié)果集全表掃描
符合條件的結(jié)果集全表掃描

雖然列索引缺點這么多,但是存儲節(jié)省帶來的成本優(yōu)勢有時還是值得我們?nèi)ミ@么做的,何況它還解決了事務性問題,需要用戶自己去權(quán)衡。值得一提的是,F(xiàn)acebook的消息應用服務器就是基于類似的方案來實現(xiàn)的。
3,ITHBase方案一中的多表,解決了性能問題,同時帶來了存儲冗余和數(shù)據(jù)一致性問題。這兩個問題中,只要解決其中一項,其實也就滿足了大多數(shù)業(yè)務場景。本方案中,著重關注的是數(shù)據(jù)一致性。ITHbase的全稱是 Indexed Transactional HBase,從名字中就能看出,事務性是它的重要特性。
ITHBase的事務原理簡介建一張事務表GLOBAL_TRX_LOG,每次開啟事務時,在表中記錄狀態(tài)。因為是基于Hbase的HTable,事務表同樣會寫WAL用于恢復,不過這個日志格式被ITHbase改造過,它稱之為THLog。客戶端對多張表更新時,先啟動事務,然后每次PUT,將事務id傳遞給HRegionServer。ITHbase通過繼承HRegionServer和HReogin類,重寫了大多數(shù)操作接口方法,比如put, update, delete, 用于獲取transactionalId和狀態(tài)。當server收到操作和事務id后,先確認服務端收到,標記當前事務為待寫入狀態(tài)(需要再發(fā)起一次PUT)。當所有表的操作完成后,由客戶端統(tǒng)一做commit寫入,做二階段提交。


4,Map-reduce這個方案沒什么好說的,存儲節(jié)省,也不需要建索引表,只需要靠強大的集群計算能力即可導出結(jié)果。但一般不適合online業(yè)務。
5,Coprocessor協(xié)處理器官方0.92.0新版正在開發(fā)中的新功能-Coprocessor,支持region級別索引。詳見:https://issues.apache.org/jira/browse/HBASE-2038
協(xié)處理器的機制可以理解為,server端添加了一些回調(diào)函數(shù)。這些回調(diào)函數(shù)如下:
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可以實現(xiàn)region級二級索引,實現(xiàn)count, sum, avg, max, min等聚合操作而不需要返回所有的數(shù)據(jù),詳見 https://issues.apache.org/jira/browse/HBASE-1512
二級索引的原理猜測因為coprocessor的最終方案還未公布,就提供的這些hooks來說,二級索引的實現(xiàn)應該是攔截同一個region的put, get, scan, delete等操作。與此同時在同一個reigon里維護一個索引CF,建立對應的索引表。基于region的索引表其實有很多局限性,比如全局排序就很難做。
不過我覺得Coprocessor最大的好處在于其提供了server端的完全擴展能力,這對于Hbase來說是一個大的躍進。
如何join?
目前還未發(fā)布,不過就了解很難從本質(zhì)上有所突破。解決方案無非就是merge和composite index,同樣事務性是需要解決的難題之一。
業(yè)界已經(jīng)公開的二級索引方案羅列:
0.19.3版Secondary Index
一直關注HBase的同學,或許知道,早在HBase 0.19.3版發(fā)布時,曾經(jīng)加入過secondary index的功能,Issue詳見這里。它的使用例子也很簡單:http://blog.rajeevsharma.in/2009/06/secondary-indexes-in-hbase.html
0.19.3版Secondary Index通過將列值以row key方法存儲,提供索引scan。但HBase早期的需求主要來自Hadoop。事務的復雜性以及當時發(fā)現(xiàn)hadoop-core里有個很難解決的與ITHBase兼容的問題,致使官方在0.20.0版將其核心代碼移出了hbase-core,改為contrib第三方擴展,Issue詳見這里
Transactional tableindexed-ITHBase****
這個方案就是在0.19.3版被官方剝離出核心的第三方擴展,它的方案上面已經(jīng)介紹過了。目前支持最新的Hbase 0.90。是否具備工業(yè)強度的穩(wěn)定性是用戶選擇它的主要障礙。
https://github.com/hbase-trx/hbase-transactional-tableindexed
Facebook方案
facebook采用的是單表多列索引的解決方案,上面已經(jīng)提到過了。很完美地解決了數(shù)據(jù)一致性問題,這主要跟他們的使用場景有關。

感興趣的同學可以看下這篇blog,本文不作詳述:
blog.huihoo.com/?p=688
HBase官方方案 0.92.0 版開發(fā)中 – Coprocessor協(xié)處理器
還未發(fā)布,不過hbase官方blog有篇介紹:http://hbaseblog.com/2010/11/30/hbase-coprocessors
Lily Hbase indexing Library
這是一個索引構(gòu)建,查詢,管理的框架。結(jié)構(gòu)上,就是通過一張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工具類。針對索引構(gòu)建場景,Hbase indexing library也提供了很方便的接口。
IHbase
IHBase非常類似ITHBase。
IHBase同樣從HBase源碼級別進行了擴展了,重新定義和實現(xiàn)了一些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的數(shù)據(jù)構(gòu)建索引。索引另一個CF的方式存儲在表內(nèi)。不過只支持region級別(類似coprocessor)scan的時候,IHBase會結(jié)合索引列中的標記,來加速scan。
http://github.com/ykulbak/ihbase

轉(zhuǎn):http://kenwublog.com/hbase-secondary-index-and-join

http://blog.csdn.net/kirayuan/article/details/6735222

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容