HBase 剖析 | HBase 讀鏈路解析
正文之前
在講HBase的讀路徑時(shí),我們先來(lái)看幾個(gè)簡(jiǎn)單的類圖。
InternalScanner是一個(gè)Interface主要提供了兩個(gè)方法,next(List<Cell> result)方法——獲取下一行的數(shù)據(jù)。而next(List<Cell> result, ScannerContext scannerContext)提供功能相同,只不過(guò)允許傳入一個(gè)ScannerContext用以記錄當(dāng)前scan任務(wù)的上下文,判斷是否可以提前結(jié)束、是否要去讀下一列、是否要去讀下一行等。并且發(fā)生在InternalScanner中的數(shù)據(jù)比較等操作,都是基于byte[](而不用先轉(zhuǎn)化為RowResults),更加接近于數(shù)據(jù)在物理上的存儲(chǔ)形式,可以獲得更高的性能。

KeyValueScanner也是一個(gè)接口,換成CellScanner可能更容易理解。對(duì),它主要提供在一個(gè)“可讀取的對(duì)象上”,獲取cell的能力。這里使用“可讀取的對(duì)象”這個(gè)詞,主要是因?yàn)樗梢允且粋€(gè)物理概念上的HFile,但也可以是邏輯意義上有迭代讀取能力的scanner。

最后一個(gè)關(guān)鍵的類就是KeyValueHeap,該類實(shí)現(xiàn)了KeyValueScanner與InternalScanner接口,具備了獲取cell及獲取行的能力。KeyValueHeap中還有一個(gè)關(guān)鍵的屬性,為heap,它是一個(gè)PriorityQueue<KeyValueScanner>對(duì)象,comparator = CellComparatorImp(即按照key的格式:rowkey:family:qualifier:timestamp)。即KeyValueHeap允許傳入多個(gè)KeyValueScanner,通過(guò)PriorityQueue的形式將這些scanner管理起來(lái),向上提供獲取cell及獲取行數(shù)據(jù)的能力!

有了InternalScanner,KeyValueScanner和KeyValueHeap其實(shí)已經(jīng)可以做很多事情了。
我們知道,HBase的查詢抽象地來(lái)看的話,是表現(xiàn)為下面這個(gè)流程的:

即從不同的HFile中進(jìn)行數(shù)據(jù)讀取,在內(nèi)存中進(jìn)行一個(gè)MergeSort,拼接成一行數(shù)據(jù)向上返回。
你們看KeyValueScanner、InternalScanner是不是就像其負(fù)責(zé)中HFile的讀取Scanner,而KeyValueHeap負(fù)責(zé)的其實(shí)就是圖中的MergeSort的任務(wù)。KeyValueHeap控制著下層KeyValueScanner、InternalScanner的數(shù)據(jù)讀取,KeyValueScanner、InternalScanner是真正讀取數(shù)據(jù)的Scanner。
好,大體的流程思路已經(jīng)講清楚了。其實(shí)HBase的讀取流程遠(yuǎn)比這復(fù)雜,涉及的對(duì)象也更多,但有了上面的基礎(chǔ)相信可以理解得很容易,接下來(lái)我們來(lái)仔細(xì)看看HBase的讀取流程。
正文
我們從RegionScanner出發(fā),仔細(xì)看看HBase的讀取流程。

上圖中的RegionScanner主要靠成員變量storeHeap,joinedHeap(KeyValueHeap)進(jìn)行數(shù)據(jù)讀取迭代。而StoreScanner也不是一個(gè)單純的Scanner,而是扮演了跟RegionScanner類似的角色,它也擁有自己的heap,以此來(lái)進(jìn)行數(shù)據(jù)的讀取。跟【正文之前】說(shuō)的一樣,KeyValueHeap控制著下層KeyValueScanner、InternalScanner的數(shù)據(jù)讀取,KeyValueScanner、InternalScanner是真正讀取數(shù)據(jù)的Scanner。只不過(guò)RegionScanner中多嵌了一層StoreScanner(KeyValueHeap),變成了這樣的調(diào)用鏈路:KeyValueHeap(RegionScanner)->KeyValueHeap(StoreScanner)
->KeyValueScanner,InternalScanner(StoreFileScanner及SegmentScanner)。
為什么HBase要這樣封裝?
其實(shí)是為了抽象不同的功能。
簡(jiǎn)單來(lái)說(shuō),
1)StoreScanner是為了聯(lián)合StoreFileScanner與SegmentScanner向上提供整行的數(shù)據(jù)迭代讀取功能。
2)而RegionScanner,一方面是對(duì)獲取的數(shù)據(jù)做了過(guò)濾功能,另一方面是為了將全部數(shù)據(jù)分為兩段獲取形式(storeHeap和joinedHeap),用以優(yōu)化性能。因?yàn)閺膕toreHeap中獲取的數(shù)據(jù)如果會(huì)被過(guò)濾,那么就沒(méi)有必要再獲取joinedHeap中的數(shù)據(jù)了。
詳細(xì)內(nèi)容我們見(jiàn)下文。
HBase的讀取任務(wù)開(kāi)始之前需要構(gòu)建初始的Scanner體系,涉及RegionScanner與StoreScanner的對(duì)象初始化,我們?cè)敿?xì)來(lái)看:
1)RegionScanner對(duì)象的初始化:
1.建立RegionScanner對(duì)象,準(zhǔn)備開(kāi)始Scan任務(wù)涉及的所有Scanner的生成。
2.根據(jù)scan任務(wù)涉及的所有column family,在本region上分別會(huì)為其中的每個(gè)column family生成一個(gè)StoreScanner。如果開(kāi)啟了on-demand column family loading,那么會(huì)根據(jù)傳入FilterList的isFamilyEssential方法進(jìn)行判斷,如果isFamilyEssential,那么會(huì)將該StoreScanner放入storeHeap中,否則放入joinedHeap中。
3.storeHeap和joinedHeap中存放StoreScanner的形式為PriorityQueue,優(yōu)先級(jí)為CellComparatorImp。
2)StoreScanner對(duì)象的初始化
接下來(lái)我們介紹RegionScanner對(duì)象的初始化中,我們一筆帶過(guò)的StoreScanner的生成過(guò)程:
1.根據(jù)scan.isReversed()控制StoreScanner中的Scanner的優(yōu)先級(jí)順序。
2.根據(jù)傳入的scan信息,生成matcher內(nèi)置對(duì)象,該對(duì)象在查詢過(guò)程中會(huì)對(duì)StoreScanner讀取的數(shù)據(jù)進(jìn)行一個(gè)篩選。
3.根據(jù)scan信息startRow,stopRow在storeEngine中查詢出涉及的HStoreFile,對(duì)這些HStoreFile分別建立StoreFileScanner,組成scannerList,并且以StoreFileComparators.SEQ_ID為優(yōu)先級(jí)(maxSequenceId升序,F(xiàn)ileSize降序,BulkTime升序,PathName升序)。
4.對(duì)scannerList根據(jù)timestamp range, row key range, bloomFilter做一個(gè)過(guò)濾。
5.scannerList中剩余的scanner根據(jù)startRow,stopRow將指針seek到正確的位置。
6.將scanners以PriorityQueue的形式組織,優(yōu)先級(jí)同樣為CellComparatorImp。
PS:StoreFileComparators.SEQ_ID —— Comparator.comparingLong(HStoreFile::getMaxSequenceId) .thenComparing(Comparator.comparingLong(new GetFileSize()).reversed()) .thenComparingLong(new GetBulkTime()).thenComparing(new GetPathName())
組建好需要Scanner體系之后,后續(xù)就是讀取流程了。
讀取流程如下圖所示:

RegionScanner主要負(fù)責(zé)以下功能:
其包含storeHeap與joinedHeap都為KeyValueHeap的對(duì)象實(shí)例,heap底層是包含了多個(gè)StoreScanner組成的PriorityQueue,comparator = CellComparatorImp。向上提供符合條件的整行數(shù)據(jù)的迭代查詢。
1.循環(huán)從storeHeap上獲取cell數(shù)據(jù),以此判斷是否還存在待獲取數(shù)據(jù)。如果沒(méi)有,return false。如果有:
2.那么先從storeHeap上獲取family essential相關(guān)的數(shù)據(jù),使用filter進(jìn)行過(guò)濾。如果被過(guò)濾,continue loop。如果沒(méi)有:
3.那么從joinedHeap上獲取剩余數(shù)據(jù),返回。
StoreScanner主要負(fù)責(zé)以下功能:
StoreScanner雖然是實(shí)現(xiàn)了KeyValueScanner和InternalScanner的類,但主要靠其成員變量heap(KeyValueHeap)來(lái)完成必要的操作。heap由多個(gè)StoreFileScanner實(shí)例按照PriorityQueue組成,comparator = CellComparatorImp。
1.循環(huán)從heap中獲取cell。
2.通過(guò)matcher匹配cell獲得返回的MatchCode,不同MatchCode會(huì)觸發(fā)不同的操作,見(jiàn)下表。
MatchCode | Operation |
INCLUDE | check fill results heap.next() continue Loop |
INCLUDE_AND_SEEK_NEXT_ROW | check fill results seekOrSkipToNextRow continue Loop |
INCLUDE_AND_SEEK_NEXT_COL | check fill results seekOrSkipToNextColumn continue Loop |
DONE | return results |
DONE_SCAN | close heap return |
SEEK_NEXT_ROW | if no data: return false; else: seekOrSkipToNextRow continue; |
SEEK_NEXT_COL | seekOrSkipToNextColumn continue; |
SKIP | heap.next() continue Loop |
SEEK_NEXT_USING_HINT | seekAsDirection or heap.next() |
3.不停循環(huán),直到數(shù)據(jù)組成整行,向上返回。
StoreScanner中KeyValueHeap的next功能:
storeScanner中的heap.next()究竟做了什么?簡(jiǎn)單來(lái)說(shuō),做了以下兩件事情:1)從current(當(dāng)前的StoreFileScanner,不在heap中)獲取cell返回。2)更新當(dāng)前current,把current放回heap重新排序,再獲取當(dāng)前最優(yōu)先的StoreFileScanner作為current。
具體做法如下:
1.從當(dāng)前的StoreFileScanner current中獲取下一個(gè)cell(kvReturn)。再獲取kvReturn往后的第一個(gè)cell(kvNext)
2.判斷kvNext是否為空。為空代表當(dāng)前current讀取完畢,需要從heap中獲取下一個(gè)scanner記為current。不為空則
3.從當(dāng)前heap中獲取第一個(gè)scanner,與current 進(jìn)行對(duì)比。判斷它們誰(shuí)通過(guò)peek()獲得的cell key最小,如果scanner更小,那么把current放回heap。重新heap.poll()獲得最新current。
4.返回kvReturn cell。
至此整個(gè)HBase的讀路徑分析結(jié)束,留待補(bǔ)充的點(diǎn):
1.Matcher的實(shí)現(xiàn)邏輯分析。
2.BloomFilter的過(guò)濾分析。
3.StoreFileScanner以下直到HDFS之間的鏈路分析,中間涉及一個(gè)BlockCache。
