Hbase

HBase存儲架構(gòu)圖

Hbase Overview.png

HBase Master

  • 為Region server分配region
  • 負(fù)責(zé)Region server的負(fù)載均衡
  • 發(fā)現(xiàn)失效的Region server并重新分配其上的region
  • HDFS上的垃圾文件回收(刪除表后的遺留文件)
  • 處理schema更新請求(對表的增刪改查)

HBase RegionServer

  • 維護(hù)master分配給他的region,處理對這些region的io請求
  • 負(fù)責(zé)切分正在運行過程中變的過大的region

HBase 數(shù)據(jù)讀取過程

Hbase里有一張?zhí)厥獾脑獢?shù)據(jù)表"hbase:meta",它保存著hbase集群中所有region所處的主機(jī)位置信息。而zookeeper中保存這這張表所在的主機(jī)位置信息。

Hbase Read.png

region定位過程

  1. client首先從zookeeper獲取meta表所在的region server
  2. client查詢出meta表中包含所需查詢key范圍的region所在region server。

同時client會緩存下region與region server的映射信息,避免重復(fù)查詢。如果region由于split或者balancing等原因改變了對應(yīng)的region server ,則client會重新從zk中查詢一遍,并再次緩存。

  1. 連接此region server,查詢。

client與region server查詢交互

  1. 查詢region server的讀緩存BlockCache 是否存在rowkey對應(yīng)數(shù)據(jù),如果有就返回,沒有的話就行進(jìn)行第二步查詢。
  2. 查詢memstore(一個按key排序的樹形結(jié)構(gòu)的緩沖區(qū)),即寫內(nèi)存是否存儲有待查rowkey數(shù)據(jù),如果有就返回,沒有進(jìn)行第三步查詢;
  3. 用Block Cache indexes和bloom filters加載對應(yīng)的HFile,根據(jù)rowkey遍歷查詢數(shù)據(jù),不管有沒有都返回到client。

HBase 數(shù)據(jù)寫入過程

write_path.png

HBase WAL(write ahead log)

WAL.png

一個regionserver上所有的region共享一個HLog,一次數(shù)據(jù)的提交是先寫WAL,再寫memstore
HLog類
實現(xiàn)了WAL的類叫做HLog,當(dāng)hregion被實例化時,HLog實例會被當(dāng)做一個參數(shù)傳到HRegion的構(gòu)造器中,當(dāng)一個Region接收到一個更新操作時,它可以直接把數(shù)據(jù)保存到一個共享的WAL實例中去.
HLog.png

HLogKey類
1、當(dāng)前的WAL使用的是hadoop的sequencefile格式,其key是HLogKey實例。HLogKey中記錄了寫入數(shù)據(jù)的歸屬信息,除了table和region名字外,同時還包括sequence number和timestamp,timestamp是“寫入時間“,sequence number的起始值為0,或者是最近一次存入文件系統(tǒng)中sequence number。Region打開存儲文件,讀取每個HFile中的最大的sequence number,如果該值大于HLog 的sequence number, 就將它作為HLog 的sequence number的值。最后當(dāng)讀取了所有hfile的sequence number,hlog也就獲得了最近一次數(shù)據(jù)持久化的位置。
2、HLog sequence File的value是HBase的KeyValue對象,即對應(yīng)HFile中的KeyValue
WALEdit類
1、客戶端發(fā)送的每個修改都會封裝成WALEdit類,一個WALEdit類包含了多個更新操作,可以說一個WALEdit就是一個原子操作,包含若干個操作的集合

LogSyncer類
1、Table在創(chuàng)建的時候,有一個參數(shù)可以設(shè)置,是否每次寫Log日志都需要往集群的其他機(jī)器同步一次,默認(rèn)是每次都同步,同步的開銷是比較大的,但不及時同步又可能因為機(jī)器宕而丟日志。同步的操作現(xiàn)在是通過pipeline的方式來實現(xiàn)的,pipeline是指datanode接收數(shù)據(jù)后,再傳給另外一臺datanode,是一種串行的方式,n-Way writes是指多datanode同時接收數(shù)據(jù),最慢的一臺結(jié)束就是整個結(jié)束,差別在于一個延遲大,一個并發(fā)高,hdfs現(xiàn)在正在開發(fā)中,以便可以選擇是按pipeline還是n-way writes來實現(xiàn)寫操作
2、Table如果設(shè)置每次不同步,則寫操作會被RegionServer緩存,并啟動一個LogSyncer線程來定時同步日志。

hbase.regionserver.optionallogflushinterval:將Hlog同步到HDFS的間隔。如果Hlog沒有積累到一定的數(shù)量,到了時間,也會觸發(fā)同步。默認(rèn)是1秒,單位毫秒。

LogRoller類

  1. 日志寫入的大小是有限制的,LogRoller類會作為一個后臺線程運行,在特定的時間間隔內(nèi)滾動日志,通過hbase.regionserver.logroll.period屬性控制,默認(rèn)1小時。所以每60分鐘,會打開一個新的log文件。久而久之,會有一大堆的文件需要維護(hù)。首先,LogRoller調(diào)用HLog.rollWriter(),定時滾動日志,之后,利用HLog.cleanOldLogs()可以清除舊的日志。它首先取得所有存儲文件HFile中的最大的sequence number,之后檢查是否存在一個log所有的條目的“sequence number”均低于這個值,如果存在,將刪除這個log。
  2. log file的數(shù)目超過了log files的最大值。這時,會強(qiáng)制調(diào)用flush out 以減少log的數(shù)目。

“hbase.regionserver.hlog.blocksize”和“hbase.regionserver.logroll.multiplier”兩個參數(shù)默認(rèn)將在log大小為SequenceFile(默認(rèn)為64MB)的95%時回滾。所以,log的大小和log使用的時間都會導(dǎo)致回滾,以先到達(dá)哪個限定為準(zhǔn)。

HBase Meta Table

HBase Meta Table.png

hbase meta 表保存所有hbase集群中所有的region信息

META Table.png
Meta Table Structure.png
  • regionId = 創(chuàng)建region時的timestamp+"."+encode值(舊版hbase的regionId只有時間戳)+"."
  • name = tablename+","+startKey+","+regionId(等同于rowkey)
  • startKey,region的開始key,第一個region的startKey是空字符串
  • endKey,region的結(jié)束key,最后一個region的endKey是空字符串。當(dāng)startKey,endKey都為空則表示只有一個region
  • encoded(Hash值),該值會作為hdfs文件系統(tǒng)中對應(yīng)region的目錄名
  • serverstartcode 是服務(wù)開始的時候的timestamp
  • server 指服務(wù)器的地址和端口
  • seqnumDuringOpen:?
hbase sample data.png

hbase原先的設(shè)計還含有一張-ROOT-表,用來保存meta表的region信息。HBase 0.96 版本移除了這個特性(HBASE-3171),并且設(shè)置meta表再大的數(shù)據(jù)量也不會像普通表一樣進(jìn)行split,因此meta表總是只有一個region(HBASE-2415)。

HBase的后臺合并

小合并:把小HFile合并成一個大HFile,這樣可以避免在讀一行的時候引用過多文件,提升讀性能。在執(zhí)行合并的時候,HBase讀出已有的多個HFile內(nèi)容,并把記錄寫入一個新文件。然后把新文件設(shè)置為激活狀態(tài),刪除所有老文件,它會占用大量的磁盤和網(wǎng)絡(luò)IO,但相比大合并還是輕量級的,可以頻繁發(fā)生。

HBase Minor Compaction.png

大合并:處理給定region的一個列族的所有HFile,將這個列族所有的HFile合并成一個文件。這個動作相當(dāng)耗費資源,可以從Shell中手工觸發(fā)大合并,這也是清理被刪除記錄的唯一機(jī)會。
HBase Major Compaction.png

從合并的操作可以看出,HBase其實不適合存儲經(jīng)常刪改的數(shù)據(jù),因為刪除的記錄在大合并前依舊占用空間,而大合并又十分耗費資源。

HBase的Delete命令并不立即刪除內(nèi)容,而是針對那個內(nèi)容寫入一條新的刪除標(biāo)記,這個刪除標(biāo)記叫做“墓碑”(tombstone)。被標(biāo)記的內(nèi)容不能在Get和Scan操作中返回結(jié)果。作為磁盤文件,HFile在非合并的時候是不能被改變的。且因為“墓碑”記錄并不一定和被刪除的記錄在同一個HFile里面,所以HFile只有在執(zhí)行一次大合并的時候才會處理墓碑記錄,被刪除記錄占用的空間才會被釋放。

Hbase Memstore&Flush

Memstore Usage in HBase ReadWrite Paths.png

用到Memstore最主要的原因是:存儲在HDFS上的數(shù)據(jù)需要按照row key 排序。而HDFS本身被設(shè)計為順序讀寫(sequential reads/writes),不允許修改。這樣的話,HBase就不能夠高效的寫數(shù)據(jù),因為要寫入到HBase的數(shù)據(jù)不會被排序,這也就意味著沒有為將來的檢索優(yōu)化。為了解決這個問題,HBase將最近接收到的數(shù)據(jù)緩存在內(nèi)存中(in Memstore),在持久化到HDFS之前完成排序,然后再快速的順序?qū)懭際DFS。
除了解決“無序”問題外,Memstore還有一些其他的好處:

  • 作為一個內(nèi)存級緩存,緩存最近增加數(shù)據(jù)。一種顯而易見的場合是,新插入數(shù)據(jù)總是比老數(shù)據(jù)頻繁使用。
  • 在持久化寫入之前,在內(nèi)存中對Rows/Cells可以做某些優(yōu)化。比如,當(dāng)數(shù)據(jù)的version被設(shè)為1的時候,對于某些CF的一些數(shù)據(jù),Memstore緩存了數(shù)個對該Cell的更新,在寫入HFile的時候,僅需要保存一個最新的版本就好了,其他的都可以直接拋棄。

每一次Memstore的flush,會為每一個ColumnFamily創(chuàng)建一個新的HFile。

MemStore的最小flush單元是Region而不是單個MemStore。可想而知,如果一個Region中Memstore過多,當(dāng)其中某一個Memstore滿足flush條件,則該region對應(yīng)的所有Memstore都會被flush,這樣每次flush的開銷必然會很大,因此我們也建議在進(jìn)行表設(shè)計的時候盡量減少ColumnFamily的個數(shù)。

Flush操作如果只選擇某個Region的Store內(nèi)的MemStore寫入磁盤,而不是統(tǒng)一寫入磁盤,那么HLog上key的一致性在Region中各個Store(ColumnFamily)下的MemStore內(nèi)就會有不一致的key區(qū)間。
如下圖所示,我們假定該RegionServer上僅有一個Region,由于不同的Row在列簇上有所區(qū)別,就會出現(xiàn)有些不同Store內(nèi)占用的內(nèi)存不一致的情況,這里會根據(jù)整體內(nèi)存使用的情況,或者RS使用內(nèi)存的情況來決定是否執(zhí)行Flush操作。如果僅僅flush使用內(nèi)存較大的memstore,那么在使用的過程中,一是Scan操作在執(zhí)行時就不夠統(tǒng)一(同一個rowkey的cf有些flush有些還沒flush),二是在HLog Replayer還原Region內(nèi)Memstore故障前的狀態(tài),不能簡單的根據(jù)Hlog的Flush_marker的標(biāo)記位來執(zhí)行Replay。

Hbase flush.png

Memstore Flush觸發(fā)條件

1. Memstore級別限制:當(dāng)Region中任意一個MemStore的大小達(dá)到了上限(hbase.hregion.memstore.flush.size,默認(rèn)128MB),會觸發(fā)Memstore刷新。
2. Region級別限制:當(dāng)Region中所有Memstore的大小總和達(dá)到了上限(hbase.hregion.memstore.block.multiplier * hbase.hregion.memstore.flush.size,默認(rèn) 2 x 128M = 256M),會觸發(fā)memstore刷新。
3. Region Server級別限制:當(dāng)一個Region Server中所有Memstore的大小總和達(dá)到了上限(hbase.regionserver.global.memstore.upperLimit * hbase_heapsize,默認(rèn) 40%的JVM內(nèi)存使用量),會觸發(fā)部分Memstore刷新。Flush順序是按照Memstore由大到小執(zhí)行,先Flush Memstore最大的Region,再執(zhí)行次大的,直至總體Memstore內(nèi)存使用量低于閾值(hbase.regionserver.global.memstore.lowerLimit * hbase_heapsize,默認(rèn)38%的JVM內(nèi)存使用量)。
4. 當(dāng)一個Region Server中HLog數(shù)量達(dá)到上限(可通過參數(shù)hbase.regionserver.max.logs配置)時,系統(tǒng)會選取最早的一個 HLog對應(yīng)的一個或多個Region進(jìn)行flush
5. HBase定期刷新Memstore:默認(rèn)周期為1小時,確保Memstore不會長時間沒有持久化。為避免所有的MemStore在同一時間都進(jìn)行flush導(dǎo)致的問題,定期的flush操作有20000左右的隨機(jī)延時。
6. 手動執(zhí)行flush:用戶可以通過shell命令 flush ‘tablename’或者flush ‘region name’分別對一個表或者一個Region進(jìn)行flush。

Memstore Flush流程
為了減少flush過程對讀寫的影響,HBase采用了類似于兩階段提交的方式,將整個flush過程分為三個階段:

  1. prepare階段:遍歷當(dāng)前Region中的所有Memstore,將Memstore中當(dāng)前數(shù)據(jù)集kvset做一個快照snapshot,然后再新建一個新的kvset。后期的所有寫入操作都會寫入新的kvset中,而整個flush階段讀操作會首先分別遍歷kvset和snapshot,如果查找不到再會到HFile中查找。prepare階段需要加一把updateLock對寫請求阻塞,結(jié)束之后會釋放該鎖。因為此階段沒有任何費時操作,因此持鎖時間很短。
  2. flush階段:遍歷所有Memstore,將prepare階段生成的snapshot持久化為臨時文件,臨時文件會統(tǒng)一放到目錄.tmp下。這個過程因為涉及到磁盤IO操作,因此相對比較耗時。
  3. commit階段:遍歷所有的Memstore,將flush階段生成的臨時文件移到指定的ColumnFamily目錄下,針對HFile生成對應(yīng)的storefile和Reader,把storefile添加到HStore的storefiles列表中,最后再清空prepare階段生成的snapshot。

上述flush流程可以通過日志信息查看:

/******* prepare階段 ********/
2016-02-04 03:32:41,516 INFO  [MemStoreFlusher.1] regionserver.HRegion: Started memstore flush for sentry_sgroup1_data,{\xD4\x00\x00\x01|\x00\x00\x03\x82\x00\x00\x00?\x06\xDA`\x13\xCAE\xD3C\xA3:_1\xD6\x99:\x88\x7F\xAA_\xD6[L\xF0\x92\xA6\xFB^\xC7\xA4\xC7\xD7\x8Fv\xCAT\xD2\xAF,1452217805884.572ddf0e8cf0b11aee2273a95bd07879., current region memstore size 128.9 M

/******* flush階段 ********/
2016-02-04 03:32:42,423 INFO  [MemStoreFlusher.1] regionserver.DefaultStoreFlusher: Flushed, sequenceid=1726212642, memsize=128.9 M, hasBloomFilter=true, into tmp file hdfs://hbase1/hbase/data/default/sentry_sgroup1_data/572ddf0e8cf0b11aee2273a95bd07879/.tmp/021a430940244993a9450dccdfdcb91d

/******* commit階段 ********/
2016-02-04 03:32:42,464 INFO  [MemStoreFlusher.1] regionserver.HStore: Added hdfs://hbase1/hbase/data/default/sentry_sgroup1_data/572ddf0e8cf0b11aee2273a95bd07879/d/021a430940244993a9450dccdfdcb91d, entries=643656, sequenceid=1726212642, filesize=7.1 M

其中第二階段flush又細(xì)分為兩個階段

  1. append階段:memstore中keyvalue首先會寫入到HFile中數(shù)據(jù)塊
  2. finalize階段:修改HFlie中meta元數(shù)據(jù)塊,索引數(shù)據(jù)塊以及Trailer數(shù)據(jù)塊等
    append流程
    具體keyvalue數(shù)據(jù)的append以及finalize過程在HFileWriterV2文件中,其中append流程可以大體表征為:
    Block Write.png

    a. 預(yù)檢查:檢查key的大小是否大于前一個key,如果大于則不符合HBase順序排列的原理,拋出異常;檢查value是否是null,如果為null也拋出異常
    b. block是否寫滿:檢查當(dāng)前Data Block是否已經(jīng)寫滿,如果沒有寫滿就直接寫入keyvalue;否則就需要執(zhí)行數(shù)據(jù)塊落盤以及索引塊修改操作;
    c. 數(shù)據(jù)落盤并修改索引:如果DataBlock寫滿,首先將block塊寫入流;再生成一個leaf index entry,寫入leaf Index block;再檢查該leaf index block是否已經(jīng)寫滿需要落盤,如果已經(jīng)寫滿,就將該leaf index block寫入到輸出流,并且為索引樹根節(jié)點root index block新增一個索引,指向葉子節(jié)點(second-level index)
    d. 生成一個新的block:重新reset輸出流,初始化startOffset為-1
    e. 寫入keyvalue:將keyvalue以流的方式寫入輸出流,同時需要寫入memstore;除此之外,如果該key是當(dāng)前block的第一個key,需要賦值給變量firstKeyInBlock
    finalize階段
    memstore中所有keyvalue都經(jīng)過append階段輸出到HFile后,會執(zhí)行一次finalize過程,主要更新HFile中meta元數(shù)據(jù)塊、索引數(shù)據(jù)塊以及Trailer數(shù)據(jù)塊,其中對索引數(shù)據(jù)塊的更新是我們關(guān)心的重點,此處詳細(xì)解析,上述append流程中c步驟’數(shù)據(jù)落盤并修改索引’會使得root index block不斷增多,當(dāng)增大到一定程度之后就需要分裂,分裂示意圖如下圖所示:
    Index Split.png

    上圖所示,分裂前索引結(jié)構(gòu)為second-level結(jié)構(gòu),圖中沒有畫出Data Blocks,根節(jié)點索引指向葉子節(jié)點索引塊。finalize階段系統(tǒng)會對Root Index Block進(jìn)行大小檢查,如果大小大于規(guī)定的大小就需要進(jìn)行分裂,圖中分裂過程實際上就是將原來的Root Index Block塊分割成4塊,每塊獨立形成中間節(jié)點InterMediate Index Block,系統(tǒng)再重新生成一個Root Index Block(圖中紅色部分),分別指向分割形成的4個interMediate Index Block。此時索引結(jié)構(gòu)就變成了third-level結(jié)構(gòu)。

Hfile文件格式

HFile的核心設(shè)計思想是(數(shù)據(jù))分塊和(索引)分級

HFile Structure.jpg

如上圖所示, HFile會被切分為多個大小相等的block塊,每個block的大小可以在創(chuàng)建表列簇的時候通過參數(shù)blocksize => ‘65535’進(jìn)行指定,默認(rèn)為64k,大號的Block有利于順序Scan,小號Block利于隨機(jī)查詢,因而需要權(quán)衡。而且所有block塊都擁有相同的數(shù)據(jù)結(jié)構(gòu),如圖左側(cè)所示,HBase將block塊抽象為一個統(tǒng)一的HFileBlock。HFileBlock支持兩種類型,一種類型不支持checksum,一種不支持。

HFile V2.png

HFile V2中,主要包括四個部分:

  • Scanned Block(數(shù)據(jù)block,表示順序掃描HFile時所有的數(shù)據(jù)塊將會被讀取,包括Leaf Index Block和Bloom Block)
  • Non-Scanned block(元數(shù)據(jù)block,表示在HFile順序掃描的時候數(shù)據(jù)不會被讀取,主要包括Meta Block和Intermediate Level Data Index Blocks兩部分)
  • Load-on-open(這部分?jǐn)?shù)據(jù)在HBase的region server啟動時需要加載到內(nèi)存中,包括FileInfo、Bloom filter block、data block index和meta block index)
  • trailer(文件尾,主要記錄了HFile的基本信息、各個部分的偏移值和尋址信息。)。

一個HFile文件包含了多種類型的HFileBlock塊,每種類型的HFileBlock主要包括兩部分:BlockHeader和BlockData。其中Header主要存儲block元數(shù)據(jù),Data用來存儲具體數(shù)據(jù)。block元數(shù)據(jù)中最核心的字段是BlockType字段,用來標(biāo)示該block塊的類型,HBase中定義了8種BlockType,每種BlockType對應(yīng)的block都存儲不同的數(shù)據(jù)內(nèi)容,有的存儲用戶數(shù)據(jù),有的存儲索引數(shù)據(jù),有的存儲meta元數(shù)據(jù)。對于任意一種類型的HFileBlock,都擁有相同結(jié)構(gòu)的BlockHeader,但是BlockData結(jié)構(gòu)卻不相同。下面通過一張表簡單羅列最核心的幾種BlockType:

HFile BlockType.png

  • Trailer Block
    主要記錄了HFile的基本信息、各個部分的偏移值和尋址信息,下圖為Trailer內(nèi)存和磁盤中的數(shù)據(jù)結(jié)構(gòu),其中只顯示了部分核心字段:
    Trailer Block.png

HFile在讀取的時候首先會解析Trailer Block并加載到內(nèi)存,然后再進(jìn)一步加載LoadOnOpen區(qū)的數(shù)據(jù),具體步驟如下:

  1. 首先加載version版本信息,HBase中version包含majorVersion和minorVersion兩部分,前者決定了HFile的主版本: V1、V2 還是V3;后者在主版本確定的基礎(chǔ)上決定是否支持一些微小修正,比如是否支持checksum等。不同的版本決定了使用不同的Reader對象對HFile進(jìn)行讀取解析
  2. 根據(jù)Version信息獲取trailer的長度(不同version的trailer長度不同),再根據(jù)trailer長度加載整個HFileTrailer Block
  3. 最后加載load-on-open部分到內(nèi)存中,起始偏移地址是trailer中的LoadOnOpenDataOffset字段,load-on-open部分的結(jié)束偏移量為HFile長度減去Trailer長度,load-on-open部分主要包括索引樹的根節(jié)點以及FileInfo兩個重要模塊,F(xiàn)ileInfo是固定長度的塊,它紀(jì)錄了文件的一些Meta信息,例如:AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY等;
  • Data Block
    DataBlock是HBase中用戶數(shù)據(jù)存儲的最小單元。DataBlock中主要存儲用戶的KeyValue數(shù)據(jù)(KeyValue后面一般會跟一個timestamp,圖中未標(biāo)出),而KeyValue結(jié)構(gòu)是HBase存儲的核心,每個數(shù)據(jù)都是以KeyValue結(jié)構(gòu)在HBase中進(jìn)行存儲。KeyValue結(jié)構(gòu)在內(nèi)存和磁盤中可以表示為:
    Data Block.png

每個KeyValue都由4個部分構(gòu)成,分別為key length,value length,key和value。其中key value和value length是兩個固定長度的數(shù)值,而key是一個復(fù)雜的結(jié)構(gòu),首先是rowkey的長度,接著是rowkey,然后是ColumnFamily的長度,再是ColumnFamily,最后是時間戳和KeyType(keytype有四種類型,分別是Put、Delete、 DeleteColumn和DeleteFamily),value就沒有那么復(fù)雜,就是一串純粹的二進(jìn)制數(shù)據(jù)。

  • BloomFilter Metadata Block & Bloom Block
    BloomFilter對于HBase的隨機(jī)讀性能至關(guān)重要,對于get操作以及部分scan操作可以剔除掉不會用到的HFile文件,減少實際IO次數(shù),提高隨機(jī)讀性能。在此簡單地介紹一下Bloom Filter的工作原理,Bloom Filter使用位數(shù)組來實現(xiàn)過濾,初始狀態(tài)下位數(shù)組每一位都為0,如下圖所示:

    BloomFilter-1.png

    假如此時有一個集合S = {x1, x2, … xn},Bloom Filter使用k個獨立的hash函數(shù),分別將集合中的每一個元素映射到{1,…,m}的范圍。對于任何一個元素,被映射到的數(shù)字作為對應(yīng)的位數(shù)組的索引,該位會被置為1。比如元素x1被hash函數(shù)映射到數(shù)字8,那么位數(shù)組的第8位就會被置為1。下圖中集合S只有兩個元素x和y,分別被3個hash函數(shù)進(jìn)行映射,映射到的位置分別為(0,2,6)和(4,7,10),對應(yīng)的位會被置為1:
    BloomFilter-2.png

    現(xiàn)在假如要判斷另一個元素是否是在此集合中,只需要被這3個hash函數(shù)進(jìn)行映射,查看對應(yīng)的位置是否有0存在,如果有的話,表示此元素肯定不存在于這個集合,否則有可能存在。下圖所示就表示z肯定不在集合{x,y}中:
    BloomFilter-3.png

    HBase中每個HFile都有對應(yīng)的位數(shù)組,KeyValue在寫入HFile時會先經(jīng)過幾個hash函數(shù)的映射,映射后將對應(yīng)的數(shù)組位改為1,get請求進(jìn)來之后再進(jìn)行hash映射,如果在對應(yīng)數(shù)組位上存在0,說明該get請求查詢的數(shù)據(jù)肯定不在該HFile中。
    HFile中的位數(shù)組就是上述Bloom Block中存儲的值,可以想象,一個HFile文件越大,里面存儲的KeyValue值越多,位數(shù)組就會相應(yīng)越大。一旦太大就不適合直接加載到內(nèi)存了,因此HFile V2在設(shè)計上將位數(shù)組進(jìn)行了拆分,拆成了多個獨立的位數(shù)組(根據(jù)Key進(jìn)行拆分,一部分連續(xù)的Key使用一個位數(shù)組)。這樣一個HFile中就會包含多個位數(shù)組,根據(jù)Key進(jìn)行查詢,首先會定位到具體的某個位數(shù)組,只需要加載此位數(shù)組到內(nèi)存進(jìn)行過濾即可,減少了內(nèi)存開支。
    在結(jié)構(gòu)上每個位數(shù)組對應(yīng)HFile中一個Bloom Block,為了方便根據(jù)Key定位具體需要加載哪個位數(shù)組,HFile V2又設(shè)計了對應(yīng)的索引Bloom Index Block,對應(yīng)的內(nèi)存和邏輯結(jié)構(gòu)圖如下:
    Bloom Index Block.png

    Bloom Index Block結(jié)構(gòu)中totalByteSize表示位數(shù)組的bit數(shù),numChunks表示Bloom Block的個數(shù),hashCount表示hash函數(shù)的個數(shù),hashType表示hash函數(shù)的類型,totalKeyCount表示bloom filter當(dāng)前已經(jīng)包含的key的數(shù)目,totalMaxKeys表示bloom filter當(dāng)前最多包含的key的數(shù)目, Bloom Index Entry對應(yīng)每一個bloom filter block的索引條目,作為索引分別指向"scanned block section" 部分的Bloom Block,Bloom Block中就存儲了對應(yīng)的位數(shù)組。
    Bloom Index Entry的結(jié)構(gòu)見上圖左邊所示,BlockOffset表示對應(yīng)Bloom Block在HFile中的偏移量,F(xiàn)irstKey表示對應(yīng)BloomBlock的第一個Key。根據(jù)上文所說,一次get請求進(jìn)來,首先會根據(jù)key在所有的索引條目中進(jìn)行二分查找,查找到對應(yīng)的Bloom Index Entry,就可以定位到該key對應(yīng)的位數(shù)組,加載到內(nèi)存進(jìn)行過濾判斷。

  • Index Block

HFile V1的時候,在數(shù)據(jù)塊索引很大時,很難全部load到內(nèi)存。假設(shè)每個數(shù)據(jù)塊使用默認(rèn)大小64KB,每個索引項64Byte,這樣如果每臺及其上存放了60TB的數(shù)據(jù),那索引數(shù)據(jù)就得有60G,所以內(nèi)存的占用還是很高的。此外,由于直到加載完所有塊索引數(shù)據(jù)之后,才能認(rèn)為region啟動完成,因此這樣的塊索引大小會明顯地拖慢region的啟動速度。所以,將這些索引以樹狀結(jié)構(gòu)進(jìn)行組織,只讓頂層索引常駐內(nèi)存,其他索引按需讀取并通過LRU cache進(jìn)行緩存,這樣就不用全部加載到內(nèi)存了。

HFile中索引結(jié)構(gòu)根據(jù)索引層級的不同分為兩種:single-level和mutil-level,前者表示單層索引,后者表示多級索引,一般為兩級或三級。HFile V1版本中只有single-level一種索引結(jié)構(gòu),V2版本中引入多級索引。
HFile V2版本Index Block則分為兩類:Root Index Block和NonRoot Index Block,其中NonRoot Index Block又分為Intermediate Index Block和Leaf Index Block兩種。HFile中索引結(jié)構(gòu)類似于一棵樹,Root Index Block表示索引數(shù)根節(jié)點,記錄每個塊首個key及其索引,Intermediate Index Block表示中間節(jié)點,記錄每個塊最后的key及其索引,Leaf Index block表示葉子節(jié)點,葉子節(jié)點直接指向?qū)嶋H數(shù)據(jù)塊。隨著dateblock數(shù)量的不斷增多,(root_index-->intermediate_index-->leaf_index-->data_block), 索引的層級會逐漸增多。

HFile index.png

HFile中除了Data Block需要索引之外,上面提到的Bloom Block也需要索引,Bloom Block的索引結(jié)構(gòu)實際上就是采用了single-level結(jié)構(gòu),是一種Root Index Block。


Root Index Block
Root Index Block表示索引樹根節(jié)點索引塊,可以作為bloom的直接索引,也可以作為data索引的根索引。而且對于single-level和mutil-level兩種索引結(jié)構(gòu)對應(yīng)的Root Index Block略有不同,這里以mutil-level的Root Index Block索引結(jié)構(gòu)為例進(jìn)行分析,在內(nèi)存和磁盤中的格式如下圖所示:

Root Index Block.png

其中Index Entry表示具體的索引對象,每個索引對象由3個字段組成,Block Offset表示索引指向數(shù)據(jù)塊的偏移量,BlockDataSize表示索引指向數(shù)據(jù)塊在磁盤上的大小,BlockKey表示索引指向數(shù)據(jù)塊中的第一個key。除此之外,還有另外3個字段用來記錄MidKey的相關(guān)信息,MidKey表示HFile所有Data Block中中間的一個Data Block,用于在對HFile進(jìn)行split操作時,快速定位HFile的中間位置。需要注意的是single-level索引結(jié)構(gòu)和mutil-level結(jié)構(gòu)相比,就只缺少MidKey這三個字段。
Root Index Block會在HFile解析的時候直接加載到內(nèi)存中,此處需要注意在Trailer Block中有一個字段為dataIndexCount,就表示此處Index Entry的個數(shù)。因為Index Entry并不定長,只有知道Entry的個數(shù)才能正確的將所有Index Entry加載到內(nèi)存。


NonRoot Index Block
當(dāng)HFile中Data Block越來越多,single-level結(jié)構(gòu)的索引已經(jīng)不足以支撐所有數(shù)據(jù)都加載到內(nèi)存,需要分化為mutil-level結(jié)構(gòu)。mutil-level結(jié)構(gòu)中NonRoot Index Block作為中間層節(jié)點或者葉子節(jié)點存在,無論是中間節(jié)點還是葉子節(jié)點,其都擁有相同的結(jié)構(gòu),如下圖所示:

NonRoot Index Block.png

和Root Index Block相同,NonRoot Index Block中最核心的字段也是Index Entry,用于指向葉子節(jié)點塊或者數(shù)據(jù)塊。不同的是,NonRoot Index Block結(jié)構(gòu)中增加了block塊的內(nèi)部索引entry Offset字段,entry Offset表示index Entry在該block中的相對偏移量(相對于第一個index Entry),用于實現(xiàn)block內(nèi)的二分查找。所有非根節(jié)點索引塊,包括Intermediate index block和leaf index block,在其內(nèi)部定位一個key的具體索引并不是通過遍歷實現(xiàn),而是使用二分查找算法,這樣可以更加高效快速地定位到待查找key。


在HFile V2中數(shù)據(jù)完整索引流程:

  1. 先在內(nèi)存中對HFile的root索引進(jìn)行二分查找,如果支持多級索引,則定位到leaf index/intermediate index,如果是單級索引,則定位到數(shù)據(jù)塊data block;
  2. 如果支持多級索引,則會從cache/hdfs中讀取leaf/intermediate index chunk,在leaf/intermediate chunk根據(jù)key值進(jìn)行二分查找(leaf/intermediate index chunk支持二分查找),找到對應(yīng)的data block。
  3. 從cache/hdfs中讀取數(shù)據(jù)塊;
  4. 在數(shù)據(jù)塊中遍歷查找對應(yīng)的數(shù)據(jù)。
data index.png

圖中紅線表示一次查詢的索引過程(HBase中相關(guān)類為HFileBlockIndex和HFileReaderV2),基本流程可以表示為:

  1. 用戶輸入rowkey為fb,在root index block中通過二分查找定位到fb在’a’和’m’之間,因此需要訪問索引’a’指向的中間節(jié)點。因為root index block常駐內(nèi)存,所以這個過程很快。
  2. 將索引’a’指向的中間節(jié)點索引塊加載到內(nèi)存,然后通過二分查找定位到fb在index ‘d’和’h’之間,接下來訪問索引’d’指向的葉子節(jié)點。
  3. 同理,將索引’d’指向的中間節(jié)點索引塊加載到內(nèi)存,一樣通過二分查找定位找到fb在index ‘f’和’g’之間,最后需要訪問索引’f’指向的數(shù)據(jù)塊節(jié)點。
  4. 將索引’f’指向的數(shù)據(jù)塊加載到內(nèi)存,通過遍歷的方式找到對應(yīng)的keyvalue。

上述流程中因為中間節(jié)點、葉子節(jié)點和數(shù)據(jù)塊都需要加載到內(nèi)存,所以io次數(shù)正常為3次。但是實際上HBase為block提供了緩存機(jī)制,可以將頻繁使用的block緩存在內(nèi)存中,可以進(jìn)一步加快實際讀取過程。所以,在HBase中,通常一次隨機(jī)讀請求最多會產(chǎn)生3次io,如果數(shù)據(jù)量小(只有一層索引),數(shù)據(jù)已經(jīng)緩存到了內(nèi)存,就不會產(chǎn)生io。


HBase表誤刪恢復(fù)

hdfs的回收站機(jī)制
在hdfs上有一個回收站的設(shè)置,可以將刪除的數(shù)據(jù)移動到回收站目錄/user/$<username>/.Trash/中,設(shè)置回收站的相關(guān)參數(shù)如下:

  • fs.trash.interval=360
    以分鐘為單位的垃圾回收時間,垃圾站中數(shù)據(jù)超過此時間,會被刪除。如果是0,垃圾回收機(jī)制關(guān)閉。可以配置在服務(wù)器端和客戶端。如果在服務(wù)器端配置trash無效,會檢查客戶端配置。如果服務(wù)器端配置有效,客戶端配置會忽略。也就是說,Server端的值優(yōu)先于Client。如有同名文件被刪除,會給文件順序編號,例如:a.txt,a.txt(1)
  • fs.trash.checkpoint.interval=0
    以分鐘為單位的垃圾回收檢查間隔。應(yīng)該小于或等于fs.trash.interval,如果是0,值等同于fs.trash.interval。該值只在服務(wù)器端設(shè)置。

如果disable+drop誤刪了hbase表數(shù)據(jù),數(shù)據(jù)不會放到回收站中,hbase有自己的一套刪除策略。
HBase的數(shù)據(jù)主要存儲在分布式文件系統(tǒng)HFile和HLog兩類文件中。Compaction操作會將合并完的不用的小Hfile移動到<.archive>文件夾,并設(shè)置ttl過期時間。HLog文件在數(shù)據(jù)完全flush到hfile中時便會過期,被移動到.oldlog(oldWALs)文件夾中。

HMaster上的定時線程HFileCleaner/LogCleaner周期性掃描.archive目錄和.oldlog目錄, 判斷目錄下的HFile或者HLog是否可以被刪除,如果可以,就直接刪除文件。

關(guān)于hfile文件和hlog文件的過期時間,其中涉及到兩個參數(shù),如下:

(1)hbase.master.logcleaner.ttl
HLog在.oldlogdir目錄中生存的最長時間,過期則被Master的線程清理,默認(rèn)是600000(ms);
(2)hbase.master.hfilecleaner.plugins
HFile的清理插件列表,逗號分隔,被HFileService調(diào)用,可以自定義,默認(rèn)org.apache.hadoop.hbase.master.cleaner.TimeToLiveHFileCleaner。

默認(rèn)hfile的失效時間是5分鐘(300000ms)。由于一般的hadoop平臺默認(rèn)都沒有對該參數(shù)的設(shè)置,可以在配置選項中添加對hbase.master.hfilecleaner.ttl的設(shè)置。

實際在測試的過程中,刪除一個hbase表,在hbase的hdfs目錄下的archive文件夾中,會立即發(fā)現(xiàn)刪除表的所有region數(shù)據(jù)(不包含regioninfo、tabledesc等元數(shù)據(jù)文件),超時5分鐘所有region(hfile)數(shù)據(jù)被刪除。
恢復(fù)步奏:

  1. 搶救數(shù)據(jù)
    保證在刪除表之后的5分鐘之內(nèi)將hdfs目錄/apps/hbase/data/archive/文件夾下的相關(guān)數(shù)據(jù)拷貝一份到另外一個安全目錄下。
  2. 新建與刪除表同名和同列族的表
  3. 將搶救下來的region數(shù)據(jù)拷貝到hbase表對應(yīng)的目錄下
hadoop fs -cp /apps/hbase/data/archive/data/default/member/0705a8ce0ead4618839b4c9cf9977fa5 /apps/hbase/data/data/default/member/
  1. hbase元數(shù)據(jù)修復(fù)
    因為被刪除的數(shù)據(jù)文件夾中并沒有包含.regioninfo文件,需要進(jìn)行元數(shù)據(jù)修復(fù)
hbase hbck -repair #一次可能不成功,多試幾次。

hbase元數(shù)據(jù)損壞
Hbase的一些啟動必要的文件放置在hdfs上,由于人為刪除了hdfs的數(shù)據(jù)塊文件,這些文件塊恰好包含了hbase的文件數(shù)據(jù),所以導(dǎo)致hbase啟動失敗。

  1. 停止hbase服務(wù)
  2. 查看hdfs的文件健康狀態(tài): hdfs fsck / | egrep -v '^.+$' | grep -v replica | grep -v Replica
  3. 根據(jù)輸出列出的所有損壞的文件塊,由于已被刪除無法恢復(fù),所以清理hdfs中損壞或缺失的數(shù)據(jù)塊。hdfs fsck -delete 或者 hdfs fs -rm /test/xxx.txt ...
  4. 再次復(fù)查hdfs的文件健康狀態(tài), 結(jié)果顯示HEALTHY。
  5. 備份hbase,hadoop fs -mv /apps/hbase /apps/hbasebak
  6. 登錄 zookeeper ,/usr/lib/zookeeper/zkCli.sh 刪除其中的hbase目錄 rmr /hbase
  7. 啟動hbase服務(wù),確認(rèn)所有相關(guān)服務(wù)都正常

hbase的行鎖與多版本并發(fā)控制(MVCC)
http://my.oschina.net/u/189445/blog/597226

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,367評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,001評論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,213評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,535評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,317評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,868評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,963評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,090評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,599評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,549評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,712評論 1 367
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,233評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,961評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,353評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,607評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,321評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,686評論 2 370

推薦閱讀更多精彩內(nèi)容