Hbase

HBase存儲架構圖

Hbase Overview.png

HBase Master

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

HBase RegionServer

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

HBase 數據讀取過程

Hbase里有一張特殊的元數據表"hbase:meta",它保存著hbase集群中所有region所處的主機位置信息。而zookeeper中保存這這張表所在的主機位置信息。

Hbase Read.png

region定位過程

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

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

  1. 連接此region server,查詢。

client與region server查詢交互

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

HBase 數據寫入過程

write_path.png

HBase WAL(write ahead log)

WAL.png

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

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

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

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

LogRoller類

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

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

HBase Meta Table

HBase Meta Table.png

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

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

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

HBase的后臺合并

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

HBase Minor Compaction.png

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

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

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

Hbase Memstore&Flush

Memstore Usage in HBase ReadWrite Paths.png

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

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

每一次Memstore的flush,會為每一個ColumnFamily創建一個新的HFile。

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

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

Hbase flush.png

Memstore Flush觸發條件

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

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

  1. prepare階段:遍歷當前Region中的所有Memstore,將Memstore中當前數據集kvset做一個快照snapshot,然后再新建一個新的kvset。后期的所有寫入操作都會寫入新的kvset中,而整個flush階段讀操作會首先分別遍歷kvset和snapshot,如果查找不到再會到HFile中查找。prepare階段需要加一把updateLock對寫請求阻塞,結束之后會釋放該鎖。因為此階段沒有任何費時操作,因此持鎖時間很短。
  2. flush階段:遍歷所有Memstore,將prepare階段生成的snapshot持久化為臨時文件,臨時文件會統一放到目錄.tmp下。這個過程因為涉及到磁盤IO操作,因此相對比較耗時。
  3. commit階段:遍歷所有的Memstore,將flush階段生成的臨時文件移到指定的ColumnFamily目錄下,針對HFile生成對應的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又細分為兩個階段

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

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

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

Hfile文件格式

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

HFile Structure.jpg

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

HFile V2.png

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

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

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

HFile BlockType.png

  • Trailer Block
    主要記錄了HFile的基本信息、各個部分的偏移值和尋址信息,下圖為Trailer內存和磁盤中的數據結構,其中只顯示了部分核心字段:
    Trailer Block.png

HFile在讀取的時候首先會解析Trailer Block并加載到內存,然后再進一步加載LoadOnOpen區的數據,具體步驟如下:

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

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

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

    BloomFilter-1.png

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

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

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

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

  • Index Block

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

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

HFile index.png

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


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

Root Index Block.png

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


NonRoot Index Block
當HFile中Data Block越來越多,single-level結構的索引已經不足以支撐所有數據都加載到內存,需要分化為mutil-level結構。mutil-level結構中NonRoot Index Block作為中間層節點或者葉子節點存在,無論是中間節點還是葉子節點,其都擁有相同的結構,如下圖所示:

NonRoot Index Block.png

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


在HFile V2中數據完整索引流程:

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

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

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

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


HBase表誤刪恢復

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

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

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

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

關于hfile文件和hlog文件的過期時間,其中涉及到兩個參數,如下:

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

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

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

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

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

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容