HBase二級索引與Join

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

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

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

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

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

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

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

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

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

211316965.jpg

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

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

211507205.jpg

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

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

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

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

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

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

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

211653941.jpg

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

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

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

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

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

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

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

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

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

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

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

TheCoprocessorinterfacedefinesthesehooks:

  • preOpen,postOpen:Calledbeforeandaftertheregionisreportedasonlinetothemaster.

  • preFlush,postFlush:Calledbeforeandafterthememstoreisflushedintoanewstorefile.

  • preCompact,postCompact:Calledbeforeandaftercompaction.

  • preSplit,postSplit:Calledaftertheregionissplit.

  • preCloseandpostClose:Calledbeforeandaftertheregionisreportedasclosedtothemaster.

TheRegionObserverinterfaceisdefinesthesehooks:

  • preGet,postGet:CalledbeforeandafteraclientmakesaGetrequest.

  • preExists,postExists:CalledbeforeandaftertheclienttestsforexistenceusingaGet.

  • prePutandpostPut:Calledbeforeandaftertheclientstoresavalue.

  • preDeleteandpostDelete:Calledbeforeandaftertheclientdeletesavalue.

  • preScannerOpenpostScannerOpen:Calledbeforeandaftertheclientopensanewscanner.

  • preScannerNext,postScannerNext:Calledbeforeandaftertheclientasksforthenextrowonascanner.

  • preScannerClose,postScannerClose:Calledbeforeandaftertheclientclosesascanner.

  • preCheckAndPut,postCheckAndPut:CalledbeforeandaftertheclientcallscheckAndPut().

  • preCheckAndDelete,postCheckAndDelete:CalledbeforeandaftertheclientcallscheckAndDelete().

利用這些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和compositeindex,一樣事務性是須要解決的難題之一。

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

0.19.3版SecondaryIndex

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

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

Transactionaltableindexed-ITHBase

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

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

Facebook方案

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

211907803.jpg

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

blog.huihoo.com/?p=688

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

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

LilyHbaseindexingLibrary

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

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

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

IHbase

IHBase很是相似ITHBase。
IHBase一樣從HBase源碼級別進行了擴展了,從新定義和實現了一些Server,Client端處理邏輯,因此,它是具有強侵入性的。
不幸的是,這個工具在fix完Hbase0.20.5版兼容bug之後再也沒更新。是否支持0.90以上版本,筆者還何嘗試。
IHBase與ITHBase的一個對比(仁者見仁)
Feature ITHBase IHBase Comment
globalordering yes no IHBasehasanindexforeachregion.TheflipsideofnothavingglobalorderingiscompatibilitywiththegoodoldHRegion:resultsarecomingbackinroworder(andnotvalueorderasinITHBase)
Fulltablescan? no no THbasedoesapartialscanontheindextable.ITHBasesupportsspecifyingstart/endrowstolimitthenumberofscannedregions
MultipleIndexUsage no yes IHBasecantakeadvantageofmultipleindexesinthesamescan.IHBaseIdxScanobjectacceptsanExpressionwhichallowsintersection/unisonofseveralindexedcolumncriteria
Extradiskstorage yes no IHBaseindexesarecreatedwhentheregionstarts/flushesanddonotrequireanyextrastorage
ExtraRAM yes yes IHBaseindexesareinmemoryandhenceincreasethememoryoverhead.THBbaseindexesincreasethenumberofregionseachregionserverhastosupportthuscostingmemorytoo
Parallelscanningsupport no yes InITHBasetheindextableneedstobeconsultedandthenGETsareissuedforeachmatchingrow.ThebehaviorofIHBase(asperceivedbytheclient)isnodifferentthanaregularscanandhencesupportsparallelscanningseamlessly.parallelGETcanbeimplementedtospeedupTHbasescans
原理簡介
在Memstore滿了之後刷磁盤時,IHBase會進行攔截請求併爲這個memstore的數據構建索引。索引另外一個CF的方式存儲在表內。不過只支持region級別(相似coprocessor)
scan的時候,IHBase會結合索引列中的標記,來加速scan。

歡迎訪問個人我的博客,獲取其餘相關資料!

相關文章
相關標籤/搜索