二級索引與索引Join是多數(shù)業(yè)務(wù)系統(tǒng)要求存儲引擎提供的基本特性,RDBMS早已支持,NOSQL陣營也在摸索著符合自身特點(diǎn)的最佳解決方案。
這篇文章會以HBase做為對象來討論如何基于Hbase構(gòu)建二級索引與實(shí)現(xiàn)索引join。文末同時會列出目前已知的包括0.19.3版secondary index,ITHbase, Facebook方案和官方Coprocessor的介紹。
理論目標(biāo)
在HBase中實(shí)現(xiàn)二級索引與索引Join需要考慮三個目標(biāo):
1,高性能的范圍檢索。
2,數(shù)據(jù)的低冗余(存儲所占的數(shù)據(jù)量)。
3,數(shù)據(jù)的一致性。
性能與數(shù)據(jù)冗余,一致性是相互制約的關(guān)系。
如果你實(shí)現(xiàn)了高性能地范圍檢索,必然需要靠冗余索引數(shù)據(jù)來提升性能,而數(shù)據(jù)冗余會導(dǎo)致更新數(shù)據(jù)時難以實(shí)現(xiàn)一致性,特別是分布式場景下。
如果你不要求高效地范圍檢索,那么可以不考慮產(chǎn)生冗余數(shù)據(jù),一致性問題也可以間接避免,畢竟share nothing是公認(rèn)的最簡單有效的解決方案。
理論結(jié)合實(shí)際,下文會以實(shí)例的方式來闡述各個方案是如何選擇偏重點(diǎn)。
這些方案是經(jīng)過筆者資料查閱和同事的不斷交流后得出的結(jié)論,如有錯誤,歡迎指正:
1,按索引建表
每一個索引建立一個表,然后依靠表的row key來實(shí)現(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,按索引的種類掃描各自獨(dú)立的單索引表,最后將掃描結(jié)果merge。
這個方案的特點(diǎn)是簡單,但是如果多個索引掃描結(jié)果數(shù)據(jù)量比較大的話,merge就會遇到瓶頸。
比如,現(xiàn)在有一張1億的用戶信息表,建有出生地和年齡兩個索引,我想得到一個條件是在杭州出生,年齡為20歲的按用戶id正序排列前10個的用戶列表。
有一種方案是,系統(tǒng)先掃描出生地為杭州的索引,得到一個用戶id結(jié)果集,這個集合的規(guī)模假設(shè)是10萬。
然后掃描年齡,規(guī)模是5萬,最后merge這些用戶id,去重,排序得到結(jié)果。
這明顯有問題,如何改良?
保證出生地和年齡的結(jié)果是排過序的,可以減少merge的數(shù)據(jù)量?但Hbase是按row key排序,value是不能排序的。
變通一下 – 將用戶id冗余到row key里?OK,這是一種解決方案了,這個方案的圖示如下:
merge時提取交集就是所需要的列表,順序是靠索引增加了_id,以字典序保證的。
2, 按索引查詢種類建立組合索引。
在方案1的場景中,想象一下,如果單索引數(shù)量多達(dá)10個會怎么樣?10個索引,就要merge 10次,性能可想而知。
解決這個問題需要參考RDBMS的組合索引實(shí)現(xiàn)。
比如出生地和年齡需要同時查詢,此時如果建立一個出生地和年齡的組合索引,查詢時效率會高出merge很多。
當(dāng)然,這個索引也需要冗余用戶id,目的是讓結(jié)果自然有序。結(jié)構(gòu)圖示如下:
這個方案的優(yōu)點(diǎn)是查詢速度非常快,根據(jù)查詢條件,只需要到一張表中檢索即可得到結(jié)果list。缺點(diǎn)是如果有多個索引,就要建立多個與查詢條件一一對應(yīng)的組合索引,存儲壓力會增大。
在制定Schema設(shè)計(jì)方案時,設(shè)計(jì)人員需要充分考慮場景的特點(diǎn),結(jié)合方案一和二來使用。下面是一個簡單的對比:
單索引 | 組合索引 | |
檢索性能 | 優(yōu)異 | 優(yōu)異 |
存儲 | 數(shù)據(jù)不冗余,節(jié)省存儲。 | 數(shù)據(jù)冗余,存儲比較浪費(fèi)。 |
事務(wù)性 | 多個索引保證事務(wù)性比較困難。 | 多個索引保證事務(wù)性比較困難。 |
join | 性能較差 | 性能優(yōu)異 |
count,sum,avg,etc | 符合條件的結(jié)果集全表掃描 | 符合條件的結(jié)果集全表掃描 |
從上表中可以得知,方案1,2都存在更新時事務(wù)性保證比較困難的問題。如果業(yè)務(wù)系統(tǒng)可以接受最終一致性的話,事務(wù)性會稍微好做一些。否則只能借助于復(fù)雜的分布式事務(wù),比如JTA,Chubby等技術(shù)。
count, sum, avg, max, min等聚合功能,Hbase只能通過硬掃的方式,并且很悲劇,你可能需要做一些hack操作(比如加一個CF,value為null),否則你在掃描時可能需要往客戶端傳回所有數(shù)據(jù)。
當(dāng)然你可以在這個場景上做一些優(yōu)化,比如增加狀態(tài)表等,但復(fù)雜性帶來的風(fēng)險(xiǎn)會更高。
還有一種終極解決方案就是在業(yè)務(wù)上只提供上一頁和下一頁,這或許是最簡單有效的方案了。
2,單張表多個列族,索引基于列
Hbase提供了列族Column Family特性。
列索引是將Column Family做為index,多個index值散落到Qualifier,多個column值依據(jù)version排列(CF, Qualifer, Version Hbase會保證有序,其中CF和Qualifier正序,Version倒序)。
舉個典型的例子,就是用戶賣了很多商品,這些商品的標(biāo)題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è)務(wù)系統(tǒng)本身做一些改進(jìn)。
如何join?
實(shí)現(xiàn)方式同方案1里的join,多個CF列索引掃描結(jié)果后,需要走merge,將多個索引的查詢結(jié)果conjunction。
兩個方案的對比似乎變化就是一個表,一個列,但其實(shí)這個方案有個最大的好處,就是解決了事務(wù)性的問題,因?yàn)樗械乃饕际歉鷨蝹€row key綁定的,我們知道單個row的更新,在hbase中是保證原子更新的,這就是這個方案的天然優(yōu)勢。當(dāng)你在考慮單索引時,使用基于列的索引會比單表索引有更好的適用性。
而組合索引在以列為存儲粒度的方案里,也同樣可以折中實(shí)現(xiàn)。理解這種存儲模式的同學(xué)可能已經(jīng)猜到了,就是基于qualifier。
下表對比了表索引和列索引的優(yōu)缺點(diǎn):
列索引 | 表索引 | |
檢索性能 | 檢索數(shù)據(jù)需要走多次scan,第一次scan row key,第二次scan qualifier,第三次scan version。 | 只需要走一次row key的scan即可。 |
存儲 | 在沒有組合索引時,存儲較節(jié)省 | 在沒有組合索引時,存儲較節(jié)省 |
事務(wù)性 | 容易保證 | 保證事務(wù)性比較困難 |
join | 性能較差,只有在建立組合條件Qualifier的時候性能會有所改善 | 性能較差,只有在建立組合表索引的時候性能會有所改善 |
額外的問題 |
1,同一個row里每個qualifier的version是有大小限制的,不能超過Int的最大值。(別以為這個值很大,對于海量數(shù)據(jù)存儲,上億很平常)
2,version的count總數(shù)需要額外做處理獲取。 3,單個row數(shù)據(jù)超過split大小時,會導(dǎo)致不能compaction或compaction內(nèi)存吃緊,增加風(fēng)險(xiǎn)。 |
|
count,sum,avg,etc | 符合條件的結(jié)果集全表掃描 | 符合條件的結(jié)果集全表掃描 |
雖然列索引缺點(diǎn)這么多,但是存儲節(jié)省帶來的成本優(yōu)勢有時還是值得我們?nèi)ミ@么做的,何況它還解決了事務(wù)性問題,需要用戶自己去權(quán)衡。
值得一提的是,F(xiàn)acebook的消息應(yīng)用服務(wù)器就是基于類似的方案來實(shí)現(xiàn)的。
3,ITHBase
方案一中的多表,解決了性能問題,同時帶來了存儲冗余和數(shù)據(jù)一致性問題。這兩個問題中,只要解決其中一項(xiàng),其實(shí)也就滿足了大多數(shù)業(yè)務(wù)場景。
本方案中,著重關(guān)注的是數(shù)據(jù)一致性。ITHbase的全稱是 Indexed Transactional HBase,從名字中就能看出,事務(wù)性是它的重要特性。
ITHBase的事務(wù)原理簡介
建一張事務(wù)表__GLOBAL_TRX_LOG__,每次開啟事務(wù)時,在表中記錄狀態(tài)。因?yàn)槭腔贖base的HTable,事務(wù)表同樣會寫WAL用于恢復(fù),不過這個日志格式被ITHbase改造過,它稱之為THLog。
客戶端對多張表更新時,先啟動事務(wù),然后每次PUT,將事務(wù)id傳遞給HRegionServer。
ITHbase通過繼承HRegionServer和HReogin類,重寫了大多數(shù)操作接口方法,比如put, update, delete, 用于獲取transactionalId和狀態(tài)。
當(dāng)server收到操作和事務(wù)id后,先確認(rèn)服務(wù)端收到,標(biāo)記當(dāng)前事務(wù)為待寫入狀態(tài)(需要再發(fā)起一次PUT)。當(dāng)所有表的操作完成后,由客戶端統(tǒng)一做commit寫入,做二階段提交。
4,Map-reduce
這個方案沒什么好說的,存儲節(jié)省,也不需要建索引表,只需要靠強(qiáng)大的集群計(jì)算能力即可導(dǎo)出結(jié)果。但一般不適合online業(yè)務(wù)。
5,Coprocessor協(xié)處理器
官方0.92.0新版正在開發(fā)中的新功能-
Coprocessor
,支持region級別索引。詳見:
https://issues.apache.org/jira/browse/HBASE-2038
協(xié)處理器的機(jī)制可以理解為,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可以實(shí)現(xiàn)region級二級索引,實(shí)現(xiàn)count, sum, avg, max, min等聚合操作而不需要返回所有的數(shù)據(jù),詳見 https://issues.apache.org/jira/browse/HBASE-1512 。
二級索引的原理猜測
因?yàn)閏oprocessor的最終方案還未公布,就提供的這些hooks來說,二級索引的實(shí)現(xiàn)應(yīng)該是攔截同一個region的put, get, scan, delete等操作。與此同時在同一個reigon里維護(hù)一個索引CF,建立對應(yīng)的索引表。
基于region的索引表其實(shí)有很多局限性,比如全局排序就很難做。
不過我覺得Coprocessor最大的好處在于其提供了server端的完全擴(kuò)展能力,這對于Hbase來說是一個大的躍進(jìn)。
如何join?
目前還未發(fā)布,不過就了解很難從本質(zhì)上有所突破。解決方案無非就是merge和composite index,同樣事務(wù)性是需要解決的難題之一。
業(yè)界已經(jīng)公開的二級索引方案羅列:
0.19.3版Secondary Index
一直關(guān)注HBase的同學(xué),或許知道,早在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。事務(wù)的復(fù)雜性以及當(dāng)時發(fā)現(xiàn)hadoop-core里有個很難解決的與ITHBase兼容的問題,致使官方在0.20.0版將其核心代碼移出了hbase-core,改為contrib第三方擴(kuò)展,Issue詳見
這里
。
Transactional tableindexed- ITHBase
這個方案就是在0.19.3版被官方剝離出核心的第三方擴(kuò)展,它的方案上面已經(jīng)介紹過了。目前支持最新的Hbase 0.90。
是否具備工業(yè)強(qiáng)度的穩(wěn)定性是用戶選擇它的主要障礙。
https://github.com/hbase-trx/hbase-transactional-tableindexed
Facebook方案
facebook采用的是單表多列索引的解決方案,上面已經(jīng)提到過了。很完美地解決了數(shù)據(jù)一致性問題,這主要跟他們的使用場景有關(guān)。
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索引表。
特點(diǎn)是,有一套非常完善的針對int, string, utf-8, decimal等類型的row key排序機(jī)制。這個機(jī)制在這篇博文中有詳細(xì)介紹:
此外,框架針對join場景(原理=merge),提供了封裝好的conjunction和disjunction工具類。
針對索引構(gòu)建場景,Hbase indexing library也提供了很方便的接口。
IHbase
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 |
scan的時候,IHBase會結(jié)合索引列中的標(biāo)記,來加速scan。
歡迎訪問我的 個人博客 ,獲取其他相關(guān)資料!
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
