HDFS只支持文件append操作, 而依賴HDFS的HBase如何完成增刪改查功能


其實HBase/LevelDB等系統,背后都應用了Log-structured merge tree。HBase的寫操作有兩個步驟:1) 寫入HLog(防止宕機丟失數據), 2) 寫入對應的Memstore(內存)。當Memstore寫滿時,其中的數據被flush到HDFS,形成一個新的數據文件(HFile)。簡單的講,HBase的update/delete操作並不影響先前寫入的字段,update只是對同一個key增加了一條記錄,而delete只是將某個key標記為刪除。由於HBase的數據文件是按key/column/timestamp有序的,所以讀取時只要按照某種策略,可能只需要讀取少數的數據文件(HFile)就能讀取到對應key的最新狀態。而系統提供了major compaction操作,該操作用於將對應列族的所有HFile合並為一個HFile,並刪除TTL過期的數據,多余的版本,以及刪除的數據。總之,LSM在某種程度上將隨機寫轉化為順序寫,這也是HBase寫性能及其優秀的原因。

這里要提到HFile的存儲格式,每一個單元格或者KeyValue在HFile中的格式如下:
row length + row key + family length + column family + column qualifier + timestamp + key type
對於更新操作來說,會重新插入一條新的數據而不是在原來數據上修改。新的數據的timestamp會大於老的數據,這樣讀取的時候,判斷時間戳就可以取出最新的數據了。
對於刪除操作來說,會插入一條新的數據,但是key type會標記成Delete狀態。同樣,讀取的時候如果取到了是Delete,而且時間是最新的,那么這條記錄肯定是被刪掉了。
所以這種設計模式也是合並操作產生的原因。但是需要注意的是,只有大合並操作才會drop掉臟數據(老數據)
 
 
 

作者:知乎用戶
鏈接:https://www.zhihu.com/question/21849618/answer/128580488
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

作者:Zavier Gao
鏈接:https://www.zhihu.com/question/21849618/answer/20067294
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
 
 

了解HBase的童鞋都知道,HBase是一種Log-Structured Merge Tree架構模式,用戶數據寫入先寫WAL,再寫緩存,滿足一定條件后緩存數據會執行flush操作真正落盤,形成一個數據文件HFile。隨着數據寫入不斷增多,flush次數也會不斷增多,進而HFile數據文件就會越來越多。然而,太多數據文件會導致數據查詢IO次數增多,因此HBase嘗試着不斷對這些文件進行合並,這個合並過程稱為Compaction。

  Compaction會從一個region的一個store中選擇一些hfile文件進行合並。合並說來原理很簡單,先從這些待合並的數據文件中讀出KeyValues,再按照由小到大排列后寫入一個新的文件中。之后,這個新生成的文件就會取代之前待合並的所有文件對外提供服務。HBase根據合並規模將Compaction分為了兩類:MinorCompaction和MajorCompaction

  Minor Compaction是指選取一些小的、相鄰的StoreFile將他們合並成一個更大的StoreFile,在這個過程中不會處理已經Deleted或Expired的Cell。一次Minor Compaction的結果是更少並且更大的StoreFile。

  Major Compaction是指將所有的StoreFile合並成一個StoreFile,這個過程還會清理三類無意義數據:被刪除的數據、TTL過期數據、版本號超過設定版本號的數據。另外,一般情況下,Major Compaction時間會持續比較長,整個過程會消耗大量系統資源,對上層業務有比較大的影響。因此線上業務都會將關閉自動觸發Major Compaction功能,改為手動在業務低峰期觸發。

  Compaction作用 | 副作用

  上文提到,隨着hfile文件數不斷增多,一次查詢就可能會需要越來越多的IO操作,延遲必然會越來越大,如下圖一所示,隨着數據寫入不斷增加,文件數不斷增多,讀取延時也在不斷變大。而執行compaction會使得文件數基本穩定,進而IO Seek次數會比較穩定,延遲就會穩定在一定范圍。然而,compaction操作重寫文件會帶來很大的帶寬壓力以及短時間IO壓力。因此可以認為,Compaction就是使用短時間的IO消耗以及帶寬消耗換取后續查詢的低延遲。從圖上來看,就是延遲有很大的毛刺,但總體趨勢基本穩定不變,見下圖二。

HBase

  為了換取后續查詢的低延遲,除了短時間的讀放大之外,Compaction對寫入也會有很大的影響。 我們首先假設一個現象:當寫請求非常多,導致不斷生成HFile,但compact的速度遠遠跟不上HFile生成的速度,這樣就會使HFile的數量會越來越多,導致讀性能急劇下降。為了避免這種情況,在HFile的數量過多的時候會限制寫請求的速度:在每次執行MemStore flush的操作前,如果HStore的HFile數超過hbase.hstore.blockingStoreFiles (默認7),則會阻塞flush操作hbase.hstore.blockingWaitTime時間,在這段時間內,如果compact操作使得HStore文件數下降到回這個值,則停止阻塞。另外阻塞超過時間后,也會恢復執行flush操作。這樣做就可以有效地控制大量寫請求的速度,但同時這也是影響寫請求速度的主要原因之一。

  可見,Compaction會使得數據讀取延遲一直比較平穩,但付出的代價是大量的讀延遲毛刺和一定的寫阻塞。

  Compaction流程

  了解了一定的背景知識后,接下來需要從全局角度對Compaction進行了解。整個Compaction始於特定的觸發條件,比如flush操作、周期性地Compaction檢查操作等。一旦觸發,HBase會將該Compaction交由一個獨立的線程處理,該線程首先會從對應store中選擇合適的hfile文件進行合並,這一步是整個Compaction的核心,選取文件需要遵循很多條件,比如文件數不能太多、不能太少、文件大小不能太大等等,最理想的情況是,選取那些承載IO負載重、文件小的文件集,實際實現中,HBase提供了多個文件選取算法:RatioBasedCompactionPolicy、ExploringCompactionPolicy和StripeCompactionPolicy等,用戶也可以通過特定接口實現自己的Compaction算法;選出待合並的文件后,HBase會根據這些hfile文件總大小挑選對應的線程池處理,最后對這些文件執行具體的合並操作。可以通過下圖簡單地梳理上述流程:

  觸發時機

  HBase中可以觸發compaction的因素有很多,最常見的因素有這么三種:Memstore Flush、后台線程周期性檢查、手動觸發。

  1. Memstore Flush: 應該說compaction操作的源頭就來自flush操作,memstore flush會產生HFile文件,文件越來越多就需要compact。因此在每次執行完Flush操作之后,都會對當前Store中的文件數進行判斷,一旦文件數# > ,就會觸發compaction。需要說明的是,compaction都是以Store為單位進行的,而在Flush觸發條件下,整個Region的所有Store都會執行compact,所以會在短時間內執行多次compaction。

  2. 后台線程周期性檢查: 后台線程CompactionChecker定期觸發檢查是否需要執行compaction,檢查周期為:hbase.server.thread.wakefrequency*hbase.server.compactchecker.interval.multiplier。和flush不同的是,該線程優先檢查文件數#是否大於,一旦大於就會觸發compaction。如果不滿足,它會接着檢查是否滿足major compaction條件,簡單來說,如果當前store中hfile的最早更新時間早於某個值mcTime,就會觸發major compaction,HBase預想通過這種機制定期刪除過期數據。上文mcTime是一個浮動值,浮動區間默認為[7-7*0.2,7+7*0.2],其中7為hbase.hregion.majorcompaction,0.2為hbase.hregion.majorcompaction.jitter,可見默認在7天左右就會執行一次major compaction。用戶如果想禁用major compaction,只需要將參數hbase.hregion.majorcompaction設為0

  3. 手動觸發:一般來講,手動觸發compaction通常是為了執行major compaction,原因有三,其一是因為很多業務擔心自動major compaction影響讀寫性能,因此會選擇低峰期手動觸發;其二也有可能是用戶在執行完alter操作之后希望立刻生效,執行手動觸發major compaction;其三是HBase管理員發現硬盤容量不夠的情況下手動觸發major compaction刪除大量過期數據;無論哪種觸發動機,一旦手動觸發,HBase會不做很多自動化檢查,直接執行合並。

  選擇合適HFile合並

  選擇合適的文件進行合並是整個compaction的核心,因為合並文件的大小以及其當前承載的IO數直接決定了compaction的效果。最理想的情況是,這些文件承載了大量IO請求但是大小很小,這樣compaction本身不會消耗太多IO,而且合並完成之后對讀的性能會有顯著提升。然而現實情況可能大部分都不會是這樣,在0.96版本和0.98版本,分別提出了兩種選擇策略,在充分考慮整體情況的基礎上選擇最佳方案。無論哪種選擇策略,都會首先對該Store中所有HFile進行一一排查,排除不滿足條件的部分文件:

  1. 排除當前正在執行compact的文件及其比這些文件更新的所有文件(SequenceId更大)

  2. 排除某些過大的單個文件,如果文件大小大於hbase.hzstore.compaction.max.size( 默認Long最大值 ),則被排除,否則會產生大量IO消耗

  經過排除的文件稱為候選文件,HBase接下來會再判斷是否滿足major compaction條件,如果滿足,就會選擇全部文件進行合並。判斷條件有下面三條,只要滿足其中一條就會執行major compaction:

  1. 用戶強制執行major compaction

  2. 長時間沒有進行compact(CompactionChecker的判斷條件2)且候選文件數小於hbase.hstore.compaction.max(默認10)

  3. Store中含有Reference文件,Reference文件是split region產生的臨時文件,只是簡單的引用文件,一般必須在compact過程中刪除

  如果不滿足major compaction條件,就必然為minor compaction,HBase主要有兩種minor策略:RatioBasedCompactionPolicy和ExploringCompactionPolicy,下面分別進行介紹:

  RatioBasedCompactionPolicy

  從老到新逐一掃描所有候選文件,滿足其中條件之一便停止掃描:

  (1)當前文件大小 < 比它更新的所有文件大小總和 * ratio,其中ratio是一個可變的比例,在高峰期時ratio為1.2,非高峰期為5,也就是非高峰期允許compact更大的文件。那什么時候是高峰期,什么時候是非高峰期呢?用戶可以配置參數hbase.offpeak.start.hour和hbase.offpeak.end.hour來設置高峰期

  (2)當前所剩候選文件數 <= hbase.store.compaction.min(默認為3)

  停止掃描后,待合並文件就選擇出來了,即為當前掃描文件+比它更新的所有文件

  ExploringCompactionPolicy

  該策略思路基本和RatioBasedCompactionPolicy相同,不同的是,Ratio策略在找到一個合適的文件集合之后就停止掃描了,而Exploring策略會記錄下所有合適的文件集合,並在這些文件集合中尋找最優解。最優解可以理解為:待合並文件數最多或者待合並文件數相同的情況下文件大小較小,這樣有利於減少compaction帶來的IO消耗。具體流程戳 這里

  需要注意的是,Ratio策略是0.94版本的默認策略,而0.96版本之后默認策略就換為了Exploring策略,在cloudera博文 《what-are-hbase-compactions》 中,作者給出了一個兩者的簡單性能對比,基本可以看出后者在節省IO方面會有10%左右的提升:

  截止到此,HBase基本上就選擇出來了待合並的文件集合,后續通過挑選合適的處理線程,就會對這些文件進行真正的合並 。

  挑選合適的線程池

  HBase實現中有一個專門的線程CompactSplitThead負責接收compact請求以及split請求,而且為了能夠獨立處理這些請求,這個線程內部構造了多個線程池:largeCompactions、smallCompactions以及splits等,其中splits線程池負責處理所有的split請求,largeCompactions和smallCompaction負責處理所有的compaction請求,其中前者用來處理大規模compaction,后者處理小規模compaction。這里需要明白三點:

  1. 上述設計目的是為了能夠將請求獨立處理,提供系統的處理性能。

  2. 哪些compaction應該分配給largeCompactions處理,哪些應該分配給smallCompactions處理?是不是Major Compaction就應該交給largeCompactions線程池處理?不對。這里有個分配原則:待compact的文件總大小如果大於值throttlePoint(可以通過參數 hbase.hregion.majorcompaction配置, 默認為2.5G),分配給largeCompactions處理,否則分配給smallCompactions處理。

  3. largeCompactions線程池和smallCompactions線程池默認都只有一個線程,用戶可以通過參數 hbase.regionserver.thread.compaction.large和hbase.regionserver.thread.compaction.small進行配置

  執行HFile文件合並

  上文一方面選出了待合並的HFile集合,一方面也選出來了合適的處理線程,萬事俱備,只欠最后真正的合並。合並流程說起來也簡單,主要分為如下幾步:

  1. 分別讀出待合並hfile文件的KV,並順序寫到位於./tmp目錄下的臨時文件中

  2. 將臨時文件移動到對應region的數據目錄

  3. 將compaction的輸入文件路徑和輸出文件路徑封裝為KV寫入WAL日志,並打上compaction標記,最后強制執行sync

  4. 將對應region數據目錄下的compaction輸入文件全部刪除

  上述四個步驟看起來簡單,但實際是很嚴謹的,具有很強的容錯性和完美的冪等性:

  1. 如果RS在步驟2之前發生異常,本次compaction會被認為失敗,如果繼續進行同樣的compaction,上次異常對接下來的compaction不會有任何影響,也不會對讀寫有任何影響。唯一的影響就是多了一份多余的數據。

  2. 如果RS在步驟2之后、步驟3之前發生異常,同樣的,僅僅會多一份冗余數據。

  3. 如果在步驟3之后、步驟4之前發生異常,RS在重新打開region之后首先會從WAL中看到標有compaction的日志,因為此時輸入文件和輸出文件已經持久化到HDFS,因此只需要根據WAL移除掉compaction輸入文件即可

  本文重點從減少IO的層面對Compaction進行了介紹,其實Compaction還是HBase刪除過期數據的唯一手段。文章下半部分着眼於Compaction的整個流程,細化分階段分別進行了梳理。通過本文的介紹,一方面希望讀者對Compaction的左右有一個清晰的認識,另一方面能夠從流程方面了解Compaction的工作原理。然而,Compaction一直是HBase整個架構體系中最重要的一環,對它的改造也從來沒有停止過,改造的重點就是上文的核心點-’選擇合適的HFile合並’,在接下來的一篇文章中會重點分析HBase在此處所作的努力~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM