HBase原理--LSM樹

HBase的一個列簇(Column Family)本質上就是一棵LSM樹(Log-StructuredMerge-Tree)。LSM樹分爲內存部分和磁盤部分。內存部分是一個維護有序數據集合的數據結構。通常來說,內存數據結構能夠選擇平衡二叉樹、紅黑樹、跳躍表(SkipList)等維護有序集的數據結構,這裏因爲考慮併發性能,HBase選擇了表現更優秀的跳躍表。磁盤部分是由一個個獨立的文件組成,每個文件又是由一個個數據塊組成。算法

LSM樹本質上和B+樹同樣,是一種磁盤數據的索引結構。但和B+樹不一樣的是,LSM樹的索引對寫入請求更友好。由於不管是何種寫入請求,LSM樹都會將寫入操做處理爲一次順序寫,而HDFS擅長的正是順序寫(且HDFS不支持隨機寫),所以基於HDFS實現的HBase採用LSM樹做爲索引是一種很合適的選擇。數組

LSM樹的索引通常由兩部分組成,一部分是內存部分,一部分是磁盤部分。內存部分通常採用跳躍表來維護一個有序的KeyValue集合。磁盤部分通常由多個內部KeyValue有序的文件組成。數據結構

1.KeyValue存儲格式

通常來講,LSM中存儲的是多個KeyValue組成的集合,每個KeyValue通常都會用一個字節數組來表示。這裏,首先須要來理解KeyValue這個字節數組的設計。併發

以HBase爲例,這個字節數組串設計如圖所示。
image.png
整體來講,字節數組主要分爲如下幾個字段。其中Rowkey、Family、Qualifier、Timestamp、Type這5個字段組成KeyValue中的key部分。異步

• keyLen:佔用4字節,用來存儲KeyValue結構中Key所佔用的字節長度。
• valueLen:佔用4字節,用來存儲KeyValue結構中Value所佔用的字節長度。
• rowkeyLen:佔用2字節,用來存儲rowkey佔用的字節長度。
• rowkeyBytes:佔用rowkeyLen個字節,用來存儲rowkey的二進制內容。
• familyLen:佔用1字節,用來存儲Family佔用的字節長度。
• familyBytes:佔用familyLen字節,用來存儲Family的二進制內容。
• qualif ierBytes:佔用qualif ierLen個字節,用來存儲Qualif ier的二進制內性能

注意,HBase並無單獨分配字節用來存儲qualif ierLen,由於能夠經過keyLen和其餘字段的長度計算出qualif ierLen。代碼以下:優化

image.png

• timestamp:佔用8字節,表示timestamp對應的long值。
• type:佔用1字節,表示這個KeyValue操做的類型,HBase內有Put、Delete、Delete Column、DeleteFamily,等等。注意,這是一個很是關鍵的字段,代表了LSM樹內存儲的不僅是數據,而是每一次操做記錄。spa

Value部分直接存儲這個KeyValue中Value的二進制內容。因此,字節數組串主要是Key部分的設計。設計

在比較這些KeyValue的大小順序時,HBase按照以下方式(僞代碼)來肯定大小關係:
image.png
注意,在HBase中,timestamp越大的KeyValue,排序越靠前。由於用戶指望優先讀取到那些版本號更新的數據。指針

上面以HBase爲例,分析了HBase的KeyValue結構設計。一般來講,在LSM樹的KeyValue中的Key部分,有3個字段必不可少:

Key的二進制內容。

一個表示版本號的64位long值,在HBase中對應timestamp;這個版本號一般表示數據的寫入前後順序,版本號越大的數據,越優先被用戶讀取。甚至會設計必定的策略,將那些版本號較小的數據過時淘汰(HBase中有TTL策略)。

type,表示這個KeyValue是Put操做,仍是Delete操做,或者是其餘寫入操做。本質上,LSM樹中存放的並不是數據自己,而是操做記錄。這對應了LSM樹(Log-Structured Merge-Tree)中Log的含義,即操做日誌。

2.多路歸併

先看一個簡單的問題:如今有K個文件,其中第i個文件內部存儲有Ni個正整數(這些整數在文件內按照從小到大的順序存儲),如何設計一個算法將K個有序文件合併成一個大的有序文件?在排序算法中,有一類排序算法叫作歸併排序,裏面就有你們熟知的兩路歸併實現。如今至關於K路歸併,所以能夠拓展一下,思路相似。對每一個文件設計一個指針,取出K個指針中數值最小的一個,而後把最小的那個指針後移,接着繼續找K個指針中數值最小的一個,繼續後移指針……直到N個文件所有讀完爲止,如圖所示。
image.png
算法複雜度分析起來也較爲容易,首先用一個最小堆來維護K個指針,每次從堆中取最小值,開銷爲logK,最多從堆中取image.png次元素。所以最壞複雜度就是image.png

3. LSM樹的索引結構

一個LSM樹的索引主要由兩部分構成:內存部分和磁盤部分。內存部分是一個ConcurrentSkipListMap,Key就是前面所說的Key部分,Value是一個字節數組。數據寫入時,直接寫入MemStore中。隨着不斷寫入,一旦內存佔用超過必定的閾值時,就把內存部分的數據導出,造成一個有序的數據文件,存儲在磁盤上。LSM樹索引結構如圖所示。內存部分導出造成一個有序數據文件的過程稱爲flush。爲了不f lush影響寫入性能,會先把當前寫入的MemStore設爲Snapshot,再也不允許新的寫入操做寫入這個Snapshot的MemStore。另開一個內存空間做爲MemStore,讓後面的數據寫入。一旦Snapshot的MemStore寫入完畢,對應內存空間就能夠釋放。這樣,就能夠經過兩個MemStore來實現穩定的寫入性能。

image.png
LSM樹索引結構

隨着寫入的增長,內存數據會不斷地刷新到磁盤上。最終磁盤上的數據文件會愈來愈多。若是數據沒有任何的讀取操做,磁盤上產生不少的數據文件對寫入並沒有影響,並且這時寫入速度是最快的,由於全部IO都是順序IO。可是,一旦用戶有讀取請求,則須要將大量的磁盤文件進行多路歸併,以後才能讀取到所需的數據。由於須要將那些Key相同的數據全局綜合起來,最終選擇出合適的版本返回給用戶,因此磁盤文件數量越多,在讀取的時候隨機讀取的次數也會越多,從而影響讀取操做的性能。

爲了優化讀取操做的性能,咱們能夠設置必定策略將選中的多個hf ile進行多路歸併,合併成一個文件。文件個數越少,則讀取數據時須要seek操做的次數越少,讀取性能則越好。

通常來講,按照選中的文件個數,咱們將compact操做分紅兩種類型。一種是major compact,是將全部的hf ile一次性多路歸併成一個文件。這種方式的好處是,合併以後只有一個文件,這樣讀取的性能確定是最高的;但它的問題是,合併全部的文件可能須要很長的時間並消耗大量的IO帶寬,因此major compact不宜使用太頻繁,適合週期性地跑。

另一種是minor compact,即選中少數幾個hf ile,將它們多路歸併成一個文件。這種方式的優勢是,能夠進行局部的compact,經過少許的IO減小文件個數,提高讀取操做的性能,適合較高頻率地跑;但它的缺點是,只合並了局部的數據,對於那些全局刪除操做,沒法在合併過程當中徹底刪除。所以,minor compact雖然能減小文件,但卻沒法完全清除那些delete操做。而major compact能徹底清理那些delete操做,保證數據的最小化。

總結:LSM樹的索引結構本質是將寫入操做所有轉化成磁盤的順序寫入,極大地提升了寫入操做的性能。可是,這種設計對讀取操做是很是不利的,由於須要在讀取的過程當中,經過歸併全部文件來讀取所對應的KV,這是很是消耗IO資源的。所以,在HBase中設計了異步的compaction來下降文件個數,達到提升讀取性能的目的。因爲HDFS只支持文件的順序寫,不支持文件的隨機寫,並且HDFS擅長的場景是大文件存儲而非小文件,因此上層HBase選擇LSM樹這種索引結構是最合適的。

文章基於《HBase原理與實踐》一書

相關文章
相關標籤/搜索