亞寵展、全球?qū)櫸锂a(chǎn)業(yè)風向標——亞洲寵物展覽會深度解析
1049
2022-05-29
華為云上的NoSQL數(shù)據(jù)庫服務CloudTable,基于Apache HBase,提供全托管式集群服務,集成了時序數(shù)據(jù)庫OpenTSDB與時空數(shù)據(jù)庫GeoMesa,在TB/PB級別的海量數(shù)據(jù)背景下,可提供ms級查詢以及千萬級TPS,點我了解詳情。
Client發(fā)送讀取請求到RegionServer
無論是Get,還是Scan,Client在發(fā)送請求到RegionServer之前,也需要先獲取路由信息:
1.定位該請求所關聯(lián)的Region
因為Get請求僅關聯(lián)一個RowKey,所以,直接定位該RowKey所關聯(lián)的Region即可。
對于Scan請求,先定位Scan的StartRow所關聯(lián)的Region。
2.往該Region所關聯(lián)的RegionServer發(fā)送讀取請求
該過程與《一條數(shù)據(jù)的HBase之旅,簡明HBase入門教程8 - 數(shù)據(jù)路由與分組打包》的"數(shù)據(jù)路由"章節(jié)所描述的流程類似,不再贅述。
如果一次Scan涉及到跨Region的讀取,讀完一個Region的數(shù)據(jù)以后,需要繼續(xù)讀取下一個Region的數(shù)據(jù),這需要在Client側(cè)不斷記錄和刷新Scan的進展信息。如果一個Region中已無更多的數(shù)據(jù),在scan請求的響應結果中會帶有提示信息,這樣可以讓Client側(cè)切換到下一個Region繼續(xù)讀取。
RegionServer如何處理讀取請求
關于Read的命題
通過前面的文章,我們已經(jīng)知道了如下這些信息:
1.一個表可能包含一個或多個Region
將HBase中擁有數(shù)億行的一個大表,橫向切割成一個個”子表“,這一個個”子表“就是Region。
2.每一個Region中關聯(lián)一個或多個列族
如果將Region看成是一個表的橫向切割,那么,一個Region中的數(shù)據(jù)列的縱向切割,稱之為一個Column Family。每一個列,都必須歸屬于一個Column Family,這個歸屬關系是在寫數(shù)據(jù)時指定的,而不是建表時預先定義。
3.每一個列族關聯(lián)一個MemStore,以及一個或多個HFiles文件
上面的關于“Region與多列族”的圖中,泛化了Column Family的內(nèi)部結構。下圖是包含MemStore與HFile的Column Family組成結構:
HFile數(shù)據(jù)文件存在于底層的HDFS中,上圖中只是為了方便闡述HFile與Column Family之間的關系。
在HBase的源碼實現(xiàn)中,將一個Column Family抽象成一個Store對象??梢赃@么簡單理解Column Family與Store的概念差異:Column Family更多的是面向用戶層可感知的邏輯概念,而Store則是源碼實現(xiàn)中的概念,是關于一個Column Family的抽象。
4.每一個MemStore中可能涉及一個Active Segment,以及一個或多個Immutable Segments
擴展到一個Region包含兩個Column Family的情形:
5.HFile由Block構成,默認地,用戶數(shù)據(jù)被按序組織成一個個64KB的Block
HFile V1的結構雖已過時,但非常有助于你理解HFile的核心設計思想:
Data Block(上圖中左側(cè)的Data塊):保存了實際的KeyValue數(shù)據(jù)。
Data Index:關于Data Block的索引信息。
HFile V2只不過在HFile V1基礎上做的演進,將Data Index信息以及BloomFilter的數(shù)據(jù)也分成了多層。
當前階段,你只需要了解到:基于一個給定的RowKey,HFile中提供的索引信息能夠快速查詢到對應的Data Block。
在重新溫習了上述內(nèi)容以后,我們也大致了解了關于HBase讀取我們所面臨的問題是什么。關于HBase Read的命題可以定義為:如何從包含1個或多個列族(每個列族包含1個或多個MemStore Segments,以及1個或多個HFiles)的Region中讀取用戶所期望的數(shù)據(jù)?默認情況下,這些數(shù)據(jù)必須是未被標記刪除的、未過期的而且是最新版本的數(shù)據(jù)。
將Get看作一類特殊的Scan
無論是讀取一行數(shù)據(jù),還是讀取指定RowKey范圍的讀取一系列數(shù)據(jù),所面臨的問題其實是類似的,因此,可以將Get看作是一種特殊的Scan,只不過它的StartRow與StopRow重疊,事實上,RegionServer側(cè)處理Get請求時的確先將Get先轉(zhuǎn)換成了一個Scan操作。
合理組織所有的KeyValue數(shù)據(jù)源
在Store/Column Family內(nèi)部,KeyValue可能存在于MemStore的Segment中,也可能存在于HFile文件中,無論是Segment還是HFile,我們統(tǒng)稱為KeyValue數(shù)據(jù)源。
在本文的第一部分介紹如何執(zhí)行Scan操作時,我們講到了Client側(cè)使用一個ResultScanner來抽象地描述一次Scan操作,ResultScanner屏蔽掉了往RegionServer發(fā)送請求以及一個Region讀取完成以后切換到下一個Region等細節(jié)信息。
初次閱讀RegionServer/Region的讀取流程所涉及的源碼時,會被各色各樣的Scanner類整的暈頭轉(zhuǎn)向,HBase使用了各種Scanner來抽象每一層/每一類KeyValue數(shù)據(jù)源的Scan操作:
關于一個Region的讀取,被封裝成一個RegionScanner對象。
每一個Store/Column Family的讀取操作,被封裝在一個StoreScanner對象中。
SegmentScanner與StoreFileScanner分別用來描述關于MemStore中的Segment以及HFile的讀取操作。
StoreFileScanner中關于HFile的實際讀取操作,由HFileScanner完成。
RegionScanner的構成如下圖所示:
在StoreScanner內(nèi)部,多個SegmentScanner與多個StoreFileScanner被組織在一個稱之為KeyValueHeap的對象中:
每一個Scanner內(nèi)部有一個指針指向當前要讀取的KeyValue,KeyValueHeap的核心是一個優(yōu)先級隊列(PriorityQueue),在這個PriorityQueue中,按照每一個Scanner當前指針所指向的KeyValue進行排序:
// 用來組織所有的Scanner
protected PriorityQueue
// PriorityQueue當前排在最前面的Scanner
protected KeyValueScanner current = null;
同樣的,RegionScanner中的多個StoreScanner,也被組織在一個KeyValueHeap對象中:
KeyValueScanner接口
KeyValueScanner定義了讀取KeyValue的基礎接口:
/**
* 查看當前Scanner中當前指針位置的KeyValue,該接口不會移動指針.
*/
Cell peek();
/**
* 返回Scanner當前指針位置的KeyValue,而后移動指針到下一個KeyValue.
*/
Cell next() throws IOException;
/**
* 將當前Scanner的指針定位到指定的KeyValue的位置,如果不存在,則定位到
* 比該Cell大的下一個KeyValue位置.Seek操作會從當前的HFile Block的開
* 始位置查找.
*/
boolean seek(Cell key) throws IOException;
/**
* 與seek接口類似,也是將當前Scanner的指針定位到指定的KeyValue的位置,
* 如果不存在,則定位到比該KeyValue大的下一個KeyValue位置.與seek接口不
* 同點在于,該操作會從上一次讀到的HFile Block的位置開始查找.
*/
boolean reseek(Cell key) throws IOException;
/**
* 與seek/reseek類似,但不同點在于采用了Lazy Seek機制來降低磁盤IO請求,
* 其原理在于充分利用Bloom Filter的判斷結果,以及待Seek的KeyValue與該
* Scanner中最大時間戳的對比,減少一些不必要的Seek操作
*/
boolean requestSeek(Cell kv, boolean forward,??boolean useBloom) throws IOException;
實現(xiàn)了KeyValueScanner接口類的主要Scanner包括:
StoreFileScanner
SegmentScanner
StoreScanner
RegionScanner初始化
RegionScanner初始化過程,包括幾個關鍵操作:
1.獲取ReadPoint
ReadPoint決定了此次Scan操作能看到哪些數(shù)據(jù)。Scan過程中新寫入的數(shù)據(jù),對此次Scan是不可見的。
2.按需選擇對應的Store,并初始化對應的StoreScanner
StoreScanner在初始化的時候,也會按需選擇對應的SegmentScanner以及StoreFileScanner,篩選規(guī)則包括:
如果一次Scan操作指定了Time Range,則只選擇與該Time Range有關的Scanners。
對于Get操作,可以通過BloomFilter過濾掉不符合條件的Scanners。
StoreScanner中篩選除了Scanner以后,會將每一個Scanner seek到Scan的StartRow位置:
通過next請求讀取一行行數(shù)據(jù)
如果將RegionScanner理解成一個內(nèi)部構造復雜的機器,而驅(qū)動這個機器運轉(zhuǎn)的動力源自Client側(cè)的一次次scan請求,scan請求通過調(diào)用RegionScanner的next方法來獲取一行行結果。
為了簡單的解釋該流程,我們先假定一個RegionScanner中僅包含一個StoreScanner,那么,這個RegionScanner中的核心讀取操作,是由StoreScanner完成的,我們進一步假定StoreScanner由4個Scanners組成(我們泛化了SegmentScanner與StoreFileScanner的區(qū)別,統(tǒng)稱為Scanner),直觀起見,在下圖中我們使用了四種不同的顏色:
每一個Scanner中都有一個current指針指向下一個即將要讀取的KeyValue,KeyValueHeap中的PriorityQueue正是按照每一個Scanner的current所指向的KeyValue進行排序。
第一次next請求,將會返回ScannerA中的Row01:FamA:Col1,而后ScannerA的指針移動到下一個KeyValue?Row01:FamA:Col2,PriorityQueue中的Scanners排序依然不變:
第二次next請求,依然返回ScannerA中的Row01:FamA:Col2,ScannerA的指針移動到下一個KeyValue Row02:FamA:Col1,此時,PriorityQueue中的Scanners排序發(fā)生了變化:
下一次next請求,將會返回ScannerB中的KeyValue…..周而復始,直到某一個Scanner所讀取的數(shù)據(jù)耗盡,該Scanner將會被close,不再出現(xiàn)在上面的PriorityQueue中。
SegmentScanner/StoreFileScanner中返回的KeyValue,包含了各種類型的KeyValue:
已被更新過的舊KeyValue
已被標記刪除但尚未被及時清理的KeyValue
已過期的尚未被及時清理的KeyValue
用來描述一次刪除操作的KeyValue(刪除還包含了多種類型)
承載最新用戶數(shù)據(jù)的普通KeyValue
因此,在StoreScanner層,需要對這些KeyValue做更復雜的邏輯校驗,這些校驗由ScanQueryMatcher完成。默認地,可作為返回數(shù)據(jù)的KeyValue,應該滿足如下條件:
KeyValue類型為Put
KeyValue所關聯(lián)的列為用戶Scan所涉及的列
KeyValue的時間戳符合Scan的TimeRange要求
版本最新
未被標記刪除
通過了Filter的過濾條件
上述條件,只針對一些普通的Scan,不同的Scan參數(shù)配置,可能會導致條件集發(fā)生變化,如Scan啟用了Raw Scan模式時,Delete類型的KeyValue也會被返回。另外,上面的這些條件所羅列的順序,也未遵循實際的檢查順序,而實際的檢查順序也是嚴格的,如果顛倒就可能會導致Bug。小米的同學就曾發(fā)現(xiàn)了這樣的一個Bug:
假設某一個列共有T1~T5五個版本, Column Family中設置的MaxVersions=3(即最大允許保留的版本)
T5 -> Value=5
T4 -> Value=4
T3 -> Value=3
T2 -> Value=2
T1 -> Value=1
如果Scan中采用了一個SingleColumnValueFilter,要求返回滿足Value<=3的所有結果。
因為MaxVersions為3,我們所期望的返回結果應該為:
T5 -> Value=5?(Value不滿足條件)
T4 -> Value=4?(Value不滿足條件)
T3 -> Value=3
T2 -> Value=2?(Version不滿足條件)
T1 -> Value=1?(Version不滿足條件)
關于多版本檢查以及Filter檢查,這里有兩種可能的順序:
Opt 1:先檢查Filter,再檢查多版本。這種情況下的返回結果為:
T5 -> Value=5?(Value不滿足條件)
T4 -> Value=4?(Value不滿足條件)
T3 -> Value=3
T2 -> Value=2
T1 -> Value=1
這種情況的返回結果就是錯誤的。
Opt 2: 先檢查多版本,再檢查Filter。這種情況下的返回結果才是預期的。
在Scanner中,如果允許讀取多個版本(由Scan#readVersions配置),那正常的讀取順序應該為:
上面這種讀取的順序與實際存在的數(shù)據(jù)的邏輯順序也是相同的。
由于不同的Scan所讀取的每一行中的數(shù)據(jù)不同,有的限定了列的數(shù)量,有的限定了版本的數(shù)量,這使得讀取時可以通過一些優(yōu)化,減少不必要的數(shù)據(jù)掃描。如某次Scan在允許讀多個版本的同時,限定了只讀取C1~C3,那么,讀取順序應該為:
最普通的Scan,其實只需要讀取每一列的最新版本即可,那讀取的順序應該為:
通過上面幾張圖,我們其實是想說明在Scanner內(nèi)部需要具備這樣的一些基礎能力:
如果只需要當前列的最新版本,那么Scanner應該可以跳過當前列的其它版本,而且將指針移到下一列的開始位置。
如果當前行的所要讀取的列都已讀完,那么,Scanner應該可以跳過該行剩余的列,將指針移動到下一行的開始位置。
我們知道KeyValueScanner定義了基礎的seek/reseek/requestSeek等接口,可以將指針移動到指定KeyValue位置。但關于指針如何移動的決策信息,由誰來提供?
這些信息也是由ScanQueryMatcher提供的。ScanQueryMatcher對每一個KeyValue的邏輯檢查結果稱之為MatchCode,MatchCode不僅包含了是否應該返回該KeyValue的結果,還可能給出了Scanner的下一步操作的提示信息。關于它的枚舉值,簡單舉例如下:
INCLUDE_AND_SEEK_NEXT_ROW
包含當前KeyValue,并提示Scanner當前行已無需繼續(xù)讀取,請Seek到下一行。
INCLUDE_AND_SEEK_NEXT_COL
包含當前KeyValue,并提示Scanner當前列已無需繼續(xù)讀取,請Seek到下一列。
無論是StoreScanner還是RegionScanner,返回的都是符合條件的KeyValue列表。這些KeyValues在RSRpcServices層被進一步組裝成Results響應給Client側(cè)。
總結
Scan涉及了太多的細節(jié)內(nèi)容,本文只粗略介紹了Scan的一些核心思路,這與本系列文章最初的定位有關,當然也受限于本文的篇幅。 本文主要介紹了如下內(nèi)容:
介紹HBase的兩種讀取模式:Get與Scan
Client如何發(fā)起一次Get請求,Get的關鍵參數(shù)
Client如何發(fā)起一次Scan請求,Scan的關鍵參數(shù)
重點介紹了RegionServer側(cè)關于Scan的處理流程
如何用Scanner來抽象描述關于Region的讀取操作
關于讀取KeyValue的基礎Scanner接口定義
RegionScanner初始化時的關鍵操作
Client側(cè)的一次次scan請求如何驅(qū)動RegionScanner內(nèi)部的讀取操作
從StoreFileScanner/SegmentScanner中讀取出來的原始KeyValue如何被合理的校驗
Scanner讀取時如何跳過一些不必要的數(shù)據(jù)
關于Scan的更多細節(jié),感興趣的同學可以自己去源碼中探尋答案:
如果第一次scan請求不能取回所有的數(shù)據(jù),下一次scan如何快速有效繼承上一次的進度?
Get/Small Scan/Large Scan在實現(xiàn)上有何不同?
ScanQueryMatcher中校驗KeyValue的詳細邏輯
關于Filter涉及多步校驗,每一步校驗是在什么地方完成的?
MinVersion與MaxVersion的定義是什么?
ScanQueryMatcher中關于多種刪除類型的語義是如何定義的?
如何限制一次Scan所占用的內(nèi)存大小以及執(zhí)行的時間?
BloomFilter在Get/Scan流程中是如何被應用的?
Scan過程中如果正在讀取的HFile文件被Compaction合并了,如何處理?
正在Scan的Region突然被遷移到其它的RegionServer中,如何繼續(xù)原來的進度繼續(xù)讀???
Reverse Scan與普通的Scan在實現(xiàn)上有何不同?
HFile的內(nèi)容在本文只粗略提及,在RegionServer側(cè)的處理流程中,關于BlockCache部分更是只字未提。本文將重點放在介紹Scan的核心思路上。
下篇文章將單獨介紹HFile的核心原理。
hbase
版權聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權內(nèi)容。