07. HBase數(shù)據(jù)存取流程解析

客戶端數(shù)據(jù)存取流程

客戶端與HBase系統(tǒng)的寫入交互階段

  1. 用戶提交put請求后,HBase客戶端會將put請求添加到本地buffer中,符合一定條件就會通過AsyncProcess異步批量提交。HBase默認(rèn)設(shè)置autoflush=true,表示put請求直接會提交給服務(wù)器進(jìn)行處理;用戶可以設(shè)置autoflush=false,這樣的話put請求會首先放到本地buffer,等到本地buffer大小超過一定閾值(默認(rèn)為2M,可以通過配置文件配置)之后才會提交。很顯然,后者采用group commit機(jī)制提交請求,可以極大地提升寫入性能,但是因?yàn)闆]有保護(hù)機(jī)制,如果客戶端崩潰的話會導(dǎo)致提交的請求丟失。

  2. 在提交之前,HBase會在元數(shù)據(jù)表.meta.中根據(jù)rowkey找到它們歸屬的region server,這個(gè)定位的過程是通過HConnection的locateRegion方法獲得的。如果是批量請求的話還會把這些rowkey按照HRegionLocation分組,每個(gè)分組可以對應(yīng)一次RPC請求。

  3. HBase會為每個(gè)HRegionLocation構(gòu)造一個(gè)遠(yuǎn)程RPC請求MultiServerCallable<Row>,然后通過rpcCallerFactory.<MultiResponse> newCaller()執(zhí)行調(diào)用,忽略掉失敗重新提交和錯(cuò)誤處理,客戶端的提交操作到此結(jié)束。

image.png

客戶端與HBase系統(tǒng)的讀取交互階段:

  1. 客戶端首先會根據(jù)配置文件中zookeeper地址連接zookeeper,并讀取/<hbase-rootdir>/meta-region-server節(jié)點(diǎn)信息,該節(jié)點(diǎn)信息存儲HBase元數(shù)據(jù)(hbase:meta)表所在的RegionServer地址以及訪問端口等信息。用戶可以通過zookeeper命令(get /<hbase-rootdir>/meta-region-server)查看該節(jié)點(diǎn)信息。
  2. 根據(jù)hbase:meta所在RegionServer的訪問信息,客戶端會將該元數(shù)據(jù)表加載到本地并進(jìn)行緩存。然后在表中確定待檢索rowkey所在的RegionServer信息。
  3. 根據(jù)數(shù)據(jù)所在RegionServer的訪問信息,客戶端會向該RegionServer發(fā)送真正的數(shù)據(jù)讀取請求。服務(wù)器端接收到該請求之后需要進(jìn)行復(fù)雜的處理,具體的處理流程將會是這個(gè)專題的重點(diǎn)。

通過上述對客戶端以及HBase系統(tǒng)的交互分析,可以基本明確兩點(diǎn):

  • 客戶端只需要配置zookeeper的訪問地址以及根目錄,就可以進(jìn)行正常的讀寫請求。不需要配置集群的RegionServer地址列表。
  • 客戶端會將hbase:meta元數(shù)據(jù)表緩存在本地,因此上述步驟中前兩步只會在客戶端第一次請求的時(shí)候發(fā)生,之后所有請求都直接從緩存中加載元數(shù)據(jù)。如果集群發(fā)生某些變化導(dǎo)致hbase:meta元數(shù)據(jù)更改,客戶端再根據(jù)本地元數(shù)據(jù)表請求的時(shí)候就會發(fā)生異常,此時(shí)客戶端需要重新加載一份最新的元數(shù)據(jù)表到本地。


    image.png

服務(wù)端數(shù)據(jù)存取流程

image.png

服務(wù)器端RegionServer接收到客戶端的寫入請求后,首先會反序列化為Put對象,然后執(zhí)行各種檢查操作,比如檢查region是否是只讀、memstore大小是否超過blockingMemstoreSize等。檢查完成之后,就會執(zhí)行如下核心操作:

100

(1)獲取行鎖、Region更新共享鎖: HBase中使用行鎖保證對同一行數(shù)據(jù)的更新都是互斥操作,用以保證更新的原子性,要么更新成功,要么失敗。

(2)開始寫事務(wù):獲取write number,用于實(shí)現(xiàn)MVCC,實(shí)現(xiàn)數(shù)據(jù)的非鎖定讀,在保證讀寫一致性的前提下提高讀取性能。

(3)寫緩存memstore:HBase中每列族都會對應(yīng)一個(gè)store,用來存儲該列數(shù)據(jù)。每個(gè)store都會有個(gè)寫緩存memstore,用于緩存寫入數(shù)據(jù)。HBase并不會直接將數(shù)據(jù)落盤,而是先寫入緩存,等緩存滿足一定大小之后再一起落盤。

(4)Append HLog:HBase使用WAL機(jī)制保證數(shù)據(jù)可靠性,即首先寫日志再寫緩存,即使發(fā)生宕機(jī),也可以通過恢復(fù)HLog還原出原始數(shù)據(jù)。該步驟就是將數(shù)據(jù)構(gòu)造為WALEdit對象,然后順序?qū)懭際Log中,此時(shí)不需要執(zhí)行sync操作。0.98版本采用了新的寫線程模式實(shí)現(xiàn)HLog日志的寫入,可以使得整個(gè)數(shù)據(jù)更新性能得到極大提升,具體原理見下一個(gè)章節(jié)。

(5)釋放行鎖以及共享鎖

(6)Sync HLog:HLog真正sync到HDFS,在釋放行鎖之后執(zhí)行sync操作是為了盡量減少持鎖時(shí)間,提升寫性能。如果Sync失敗,執(zhí)行回滾操作將memstore中已經(jīng)寫入的數(shù)據(jù)移除。

(7)結(jié)束寫事務(wù):此時(shí)該線程的更新操作才會對其他讀請求可見,更新才實(shí)際生效。具體分析見文章數(shù)據(jù)庫事務(wù)系列-HBase行級事務(wù)模型》

(8)flush memstore:當(dāng)寫緩存滿64M之后,會啟動flush線程將數(shù)據(jù)刷新到硬盤。刷新操作涉及到HFile相關(guān)結(jié)構(gòu),后面會詳細(xì)對此進(jìn)行介紹。

RegionServer接收到客戶端的get/scan請求之后,先后做了兩件事情:構(gòu)建scanner體系(實(shí)際上就是做一些scan前的準(zhǔn)備工作),在此體系基礎(chǔ)上一行一行檢索。舉個(gè)不太合適但易于理解的例子,scan數(shù)據(jù)就和開發(fā)商蓋房一樣,也是分成兩步:組建施工隊(duì)體系,明確每個(gè)工人的職責(zé);一層一層蓋樓。


image.png

構(gòu)建scanner體系-組建施工隊(duì)

scanner體系的核心在于三層scanner:RegionScanner、StoreScanner以及StoreFileScanner。三者是層級的關(guān)系,一個(gè)RegionScanner由多個(gè)StoreScanner構(gòu)成,一張表由多個(gè)列族組成,就有多少個(gè)StoreScanner負(fù)責(zé)該列族的數(shù)據(jù)掃描。一個(gè)StoreScanner又是由多個(gè)StoreFileScanner組成。每個(gè)Store的數(shù)據(jù)由內(nèi)存中的MemStore和磁盤上的StoreFile文件組成,相對應(yīng)的,StoreScanner對象會雇傭一個(gè)MemStoreScanner和N個(gè)StoreFileScanner來進(jìn)行實(shí)際的數(shù)據(jù)讀取,每個(gè)StoreFile文件對應(yīng)一個(gè)StoreFileScanner,注意:StoreFileScanner和MemstoreScanner是整個(gè)scan的最終執(zhí)行者。

對應(yīng)于建樓項(xiàng)目,一棟樓通常由好幾個(gè)單元樓構(gòu)成(每個(gè)單元樓對應(yīng)于一個(gè)Store),每個(gè)單元樓會請一個(gè)監(jiān)工(StoreScanner)負(fù)責(zé)該單元樓的建造。而監(jiān)工一般不做具體的事情,他負(fù)責(zé)招募很多工人(StoreFileScanner),這些工人才是建樓的主體。下圖是整個(gè)構(gòu)建流程圖:

818160
  1. RegionScanner會根據(jù)列族構(gòu)建StoreScanner,有多少列族就構(gòu)建多少StoreScanner,用于負(fù)責(zé)該列族的數(shù)據(jù)檢索

1.1 構(gòu)建StoreFileScanner:每個(gè)StoreScanner會為當(dāng)前該Store中每個(gè)HFile構(gòu)造一個(gè)StoreFileScanner,用于實(shí)際執(zhí)行對應(yīng)文件的檢索。同時(shí)會為對應(yīng)Memstore構(gòu)造一個(gè)MemstoreScanner,用于執(zhí)行該Store中Memstore的數(shù)據(jù)檢索。該步驟對應(yīng)于監(jiān)工在人才市場招募建樓所需的各種類型工匠。

1.2 過濾淘汰StoreFileScanner:根據(jù)Time Range以及RowKey Range對StoreFileScanner以及MemstoreScanner進(jìn)行過濾,淘汰肯定不存在待檢索結(jié)果的Scanner。上圖中StoreFile3因?yàn)闄z查RowKeyRange不存在待檢索Rowkey所以被淘汰。該步驟針對具體的建樓方案,裁撤掉部分不需要的工匠,比如這棟樓不需要地暖安裝,對應(yīng)的工匠就可以撤掉。

1.3 Seek rowkey:所有StoreFileScanner開始做準(zhǔn)備工作,在負(fù)責(zé)的HFile中定位到滿足條件的起始Row。工匠也開始準(zhǔn)備自己的建造工具,建造材料,找到自己的工作地點(diǎn),等待一聲命下。就像所有重要項(xiàng)目的準(zhǔn)備工作都很核心一樣,Seek過程(此處略過Lazy Seek優(yōu)化)也是一個(gè)很核心的步驟,它主要包含下面三步:

  • 定位Block Offset:在Blockcache中讀取該HFile的索引樹結(jié)構(gòu),根據(jù)索引樹檢索對應(yīng)RowKey所在的Block Offset和Block Size
  • Load Block:根據(jù)BlockOffset首先在BlockCache中查找Data Block,如果不在緩存,再在HFile中加載
  • Seek Key:在Data Block內(nèi)部通過二分查找的方式定位具體的RowKey

整體流程細(xì)節(jié)參見《HBase原理-探索HFile索引機(jī)制》,文中詳細(xì)說明了HFile索引結(jié)構(gòu)以及如何通過索引結(jié)構(gòu)定位具體的Block以及RowKey

1.4 StoreFileScanner合并構(gòu)建最小堆:將該Store中所有StoreFileScanner和MemstoreScanner合并形成一個(gè)heap(最小堆),所謂heap是一個(gè)優(yōu)先級隊(duì)列,隊(duì)列中元素是所有scanner,排序規(guī)則按照scanner seek到的keyvalue大小由小到大進(jìn)行排序。這里需要重點(diǎn)關(guān)注三個(gè)問題,首先為什么這些Scanner需要由小到大排序,其次keyvalue是什么樣的結(jié)構(gòu),最后,keyvalue誰大誰小是如何確定的:

  • 為什么這些Scanner需要由小到大排序?

最直接的解釋是scan的結(jié)果需要由小到大輸出給用戶,當(dāng)然,這并不全面,最合理的解釋是只有由小到大排序才能使得scan效率最高。舉個(gè)簡單的例子,HBase支持?jǐn)?shù)據(jù)多版本,假設(shè)用戶只想獲取最新版本,那只需要將這些數(shù)據(jù)由最新到最舊進(jìn)行排序,然后取隊(duì)首元素返回就可以。那么,如果不排序,就只能遍歷所有元素,查看符不符合用戶查詢條件。這就是排隊(duì)的意義。

工匠們也需要排序,先做地板的排前面,做墻體的次之,最后是做門窗戶的。做墻體的內(nèi)部還需要再排序,做內(nèi)墻的排前面,做外墻的排后面,這樣,假如設(shè)計(jì)師臨時(shí)決定不做外墻的話,就可以直接跳過外墻部分工作。很顯然,如果不排序的話,是沒辦法臨時(shí)做決定的,因?yàn)檫@部分工作已經(jīng)可能做掉了。

  • HBase中KeyValue是什么樣的結(jié)構(gòu)?

HBase中KeyValue并不是簡單的KV數(shù)據(jù)對,而是一個(gè)具有復(fù)雜元素的結(jié)構(gòu)體,其中Key由RowKey,ColumnFamily,Qualifier ,TimeStamp,KeyType等多部分組成,Value是一個(gè)簡單的二進(jìn)制數(shù)據(jù)。Key中元素KeyType表示該KeyValue的類型,取值分別為Put/Delete/Delete Column/Delete Family等。KeyValue可以表示為如下圖所示:

99091

了解了KeyValue的邏輯結(jié)構(gòu)后,我們不妨再進(jìn)一步從原理的角度想想HBase的開發(fā)者們?yōu)槭裁慈绱藢ζ湓O(shè)計(jì)。這個(gè)就得從HBase所支持的數(shù)據(jù)操作說起了,HBase支持四種主要的數(shù)據(jù)操作,分別是Get/Scan/Put/Delete,其中Get和Scan代表數(shù)據(jù)查詢,Put操作代表數(shù)據(jù)插入或更新(如果Put的RowKey不存在則為插入操作、否則為更新操作),特別需要注意的是HBase中更新操作并不是直接覆蓋修改原數(shù)據(jù),而是生成新的數(shù)據(jù),新數(shù)據(jù)和原數(shù)據(jù)具有不同的版本(時(shí)間戳);Delete操作執(zhí)行數(shù)據(jù)刪除,和數(shù)據(jù)更新操作相同,HBase執(zhí)行數(shù)據(jù)刪除并不會馬上將數(shù)據(jù)從數(shù)據(jù)庫中永久刪除,而只是生成一條刪除記錄,最后在系統(tǒng)執(zhí)行文件合并的時(shí)候再統(tǒng)一刪除。

HBase中更新刪除操作并不直接操作原數(shù)據(jù),而是生成一個(gè)新紀(jì)錄,那問題來了,如何知道一條記錄到底是插入操作還是更新操作亦或是刪除操作呢?這正是KeyType和Timestamp的用武之地。上文中提到KeyType取值為分別為Put/Delete/Delete Column/Delete Family四種,如果KeyType取值為Put,表示該條記錄為插入或者更新操作,而無論是插入或者更新,都可以使用版本號(Timestamp)對記錄進(jìn)行選擇;如果KeyType為Delete,表示該條記錄為整行刪除操作;相應(yīng)的KeyType為Delete Column和Delete Family分別表示刪除某行某列以及某行某列族操作;

  • 不同KeyValue之間如何進(jìn)行大小比較?

上文提到KeyValue中Key由RowKey,ColumnFamily,Qualifier ,TimeStamp,KeyType等5部分組成,HBase設(shè)定Key大小首先比較RowKey,RowKey越小Key就越??;RowKey如果相同就看CF,CF越小Key越??;CF如果相同看Qualifier,Qualifier越小Key越??;Qualifier如果相同再看Timestamp,Timestamp越大表示時(shí)間越新,對應(yīng)的Key越小。如果Timestamp還相同,就看KeyType,KeyType按照DeleteFamily -> DeleteColumn -> Delete -> Put 順序依次對應(yīng)的Key越來越大。

  1. StoreScanner合并構(gòu)建最小堆:上文討論的是一個(gè)監(jiān)工如何構(gòu)建自己的工匠師團(tuán)隊(duì)以及工匠師如何做準(zhǔn)備工作、排序工作。實(shí)際上,監(jiān)工也需要進(jìn)行排序,比如一單元的監(jiān)工排前面,二單元的監(jiān)工排之后… StoreScanner一樣,列族小的StoreScanner排前面,列族大的StoreScanner排后面。

scan查詢-層層建樓

構(gòu)建Scanner體系是為了更好地執(zhí)行scan查詢,就像組建工匠師團(tuán)隊(duì)就是為了蓋房子一樣。scan查詢總是一行一行查詢的,先查第一行的所有數(shù)據(jù),再查第二行的所有數(shù)據(jù),但每一行的查詢流程卻沒有什么本質(zhì)區(qū)別。蓋房子也一樣,無論是蓋8層還是蓋18層,都需要一層一層往上蓋,而且每一層的蓋法并沒有什么區(qū)別。所以實(shí)際上我們只需要關(guān)注其中一行數(shù)據(jù)是如何查詢的就可以。

對于一行數(shù)據(jù)的查詢,又可以分解為多個(gè)列族的查詢,比如RowKey=row1的一行數(shù)據(jù)查詢,首先查詢列族1上該行的數(shù)據(jù)集合,再查詢列族2里該行的數(shù)據(jù)集合。同樣是蓋第一層房子,先蓋一單元的一層,再改二單元的一層,蓋完之后才算一層蓋完,接著開始蓋第二層。所以我們也只需要關(guān)注某一行某個(gè)列族的數(shù)據(jù)是如何查詢的就可以。

還記得Scanner體系構(gòu)建的最終結(jié)果是一個(gè)由StoreFileScanner和MemstoreScanner組成的heap(最小堆)么,這里就派上用場了。下圖是一張表的邏輯視圖,該表有兩個(gè)列族cf1和cf2(我們只關(guān)注cf1),cf1只有一個(gè)列name,表中有5行數(shù)據(jù),其中每個(gè)cell基本都有多個(gè)版本。cf1的數(shù)據(jù)假如實(shí)際存儲在三個(gè)區(qū)域,memstore中有r2和r4的最新數(shù)據(jù),hfile1中是最早的數(shù)據(jù)?,F(xiàn)在需要查詢RowKey=r2的數(shù)據(jù),按照上文的理論對應(yīng)的Scanner指向就如圖所示:

544501

這三個(gè)Scanner組成的heap為<MemstoreScanner,StoreFileScanner2, StoreFileScanner1>,Scanner由小到大排列。查詢的時(shí)候首先pop出heap的堆頂元素,即MemstoreScanner,得到keyvalue = r2:cf1:name:v3:name23的數(shù)據(jù),拿到這個(gè)keyvalue之后,需要進(jìn)行如下判定:

  1. 檢查該KeyValue的KeyType是否是Deleted/DeletedCol等,如果是就直接忽略該列所有其他版本,跳到下列(列族)
  2. 檢查該KeyValue的Timestamp是否在用戶設(shè)定的Timestamp Range范圍,如果不在該范圍,忽略
  3. 檢查該KeyValue是否滿足用戶設(shè)置的各種filter過濾器,如果不滿足,忽略
  4. 檢查該KeyValue是否滿足用戶查詢中設(shè)定的版本數(shù),比如用戶只查詢最新版本,則忽略該cell的其他版本;反正如果用戶查詢所有版本,則還需要查詢該cell的其他版本。

現(xiàn)在假設(shè)用戶查詢所有版本而且該keyvalue檢查通過,此時(shí)當(dāng)前的堆頂元素需要執(zhí)行next方法去檢索下一個(gè)值,并重新組織最小堆。即圖中MemstoreScanner將會指向r4,重新組織最小堆之后最小堆將會變?yōu)?lt;StoreFileScanner2, StoreFileScanner1, MemstoreScanner>,堆頂元素變?yōu)镾toreFileScanner2,得到keyvalue=r2:cf1:name:v2:name22,進(jìn)行一系列判定,再next,再重新組織最小堆…

不斷重復(fù)這個(gè)過程,直至一行數(shù)據(jù)全部被檢索得到。繼續(xù)下一行…

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • HBase那些事 @(大數(shù)據(jù)工程學(xué)院)[HBase, Hadoop, 優(yōu)化, HadoopChen, hbase]...
    分癡閱讀 4,098評論 3 17
  • 一、簡介 Hbase:全名Hadoop DataBase,是一種開源的,可伸縮的,嚴(yán)格一致性(并非最終一致性)的分...
    菜鳥小玄閱讀 2,589評論 0 12
  • HBase存儲架構(gòu)圖 HBase Master 為Region server分配region 負(fù)責(zé)Region s...
    kimibob閱讀 5,751評論 0 52
  • 自己認(rèn)為的自己是什么樣的?別人認(rèn)為的自己是什么樣的?客觀的你又是什么樣的?不要隨意給自己貼標(biāo)簽,因?yàn)槲Ψ▌t的威...
    鵬鵬YH閱讀 367評論 0 0

友情鏈接更多精彩內(nèi)容