1. 程式人生 > >B樹、B+樹、LSM樹以及其典型應用場景

B樹、B+樹、LSM樹以及其典型應用場景

前言

動態查詢樹主要有:二叉查詢樹、平衡二叉樹、紅黑樹、B樹、B+樹。前面三種是典型的二叉查詢樹,查詢的時間複雜度是O(log2N)與樹的深度有關係,那麼降低樹的深度也就可以提升查詢效率。這時就提出了平衡多路查詢樹,也就是B樹以及B+樹。

B樹和B+樹非常典型的場景就是用於關係型資料庫的索引(MySQL)

B樹

B樹是一種平衡多路搜尋樹,B樹與紅黑樹最大的不同在於,B樹的結點可以有多個子女,從幾個到幾千個。那為什麼又說B樹與紅黑樹很相似呢?因為與紅黑樹一樣,一棵含n個結點的B樹的高度也為O(lgn),但可能比一棵紅黑樹的高度小許多,應為它的分支因子比較大。所以,B樹可以在O(logn)時間內,實現各種如插入(insert),刪除(delete)等動態集合操作。

B樹的定義如下:

  • 根節點至少有兩個子節點
  • 每個節點有M-1個key,並且以升序排列
  • 位於M-1和M key的子節點的值位於M-1 和M key對應的Value之間
  • 其它節點至少有M/2個子節點
  • 所有葉子結點位於同一層;

下圖是一個M=4的4階的B樹:
這裡寫圖片描述

B樹的搜尋:從根結點開始,對結點內的關鍵字(有序)序列進行二分查詢,如果命中則結束,否則進入查詢關鍵字所屬範圍的兒子結點;重複,直到所對應的兒子指標為空,或已經是葉子結點;

B樹的特性:

  1. 關鍵字集合分佈在整顆樹中;
  2. 任何一個關鍵字出現且只出現在一個結點中;
  3. 搜尋有可能在非葉子結點結束(樹中所有結點都儲存資料,與B+樹這一點不同);
  4. 其搜尋效能等價於在關鍵字全集內做一次二分查詢;

下面是一個B樹插入的演示動畫,依次插入:
6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4

這裡寫圖片描述

B+樹

B+樹是對B樹的一種變形,與B樹的差異在於:

  1. 有n棵子樹的結點中含有n個關鍵字,每個關鍵字不儲存資料,只用來索引,所有資料都儲存在葉子節點。
  2. 所有的葉子結點中包含了全部關鍵字的資訊,及指向含這些關鍵字記錄的指標,且葉子結點本身依關鍵字的大小自小而大順序連結。
  3. 所有的非終端結點可以看成是索引部分,結點中僅含其子樹(根結點)中的最大(或最小)關鍵字。
  4. 為所有葉子結點增加一個鏈指標,便於區間查詢和遍歷。
  5. 所有關鍵字都在葉子結點出現;

如下圖一個M=3 的B+樹:

這裡寫圖片描述

B+樹的搜尋:與B-樹也基本相同,區別是B+樹只有達到葉子結點才命中(B-樹可以在非葉子結點命中),其效能也等價於在關鍵字全集做一次二分查詢;

B+的特性:

  1. 非葉子結點相當於是葉子結點的索引(稀疏索引),葉子結點相當於是儲存(關鍵字)資料的資料層;
  2. B+樹的葉子結點都是相鏈的,因此對整棵樹的遍歷只需要一次線性遍歷葉子結點即可。而且由於資料順序排列並且相連,所以便於區間查詢和搜尋。而B樹則需要進行每一層的遞迴遍歷。相鄰的元素可能在記憶體中不相鄰,所以快取命中性沒有B+樹好。

B樹和B+樹總結:

B樹:多路搜尋樹,每個結點儲存M/2到M個關鍵字,非葉子結點儲存指向關鍵字範圍的子結點;所有關鍵字在整顆樹中出現,且只出現一次,非葉子結點可以命中;

B+樹:在B-樹基礎上,為葉子結點增加連結串列指標,所有關鍵字都在葉子結點中出現,非葉子結點作為葉子結點的索引;B+樹總是到葉子結點才命中;

B+樹雖然優點很多,但是B樹也有優點,其優點在於,由於B樹的每一個節點都包含key和value,因此經常訪問的元素可能離根節點更近,因此訪問也更迅速。下面是B 樹和B+樹的區別圖:

這裡寫圖片描述

為什麼說B+tree比B樹更適合實際應用中作業系統的檔案索引和資料庫索引?

(1) B+tree的磁碟讀寫代價更低
B+tree的內部結點並沒有指向關鍵字具體資訊的指標。因此其內部結點相對B樹更小。如果把所有同一內部結點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀入記憶體中的需要查詢的關鍵字也就越多。相對來說IO讀寫次數也就降低了。

舉個例子,假設磁碟中的一個盤塊容納16bytes,而一個關鍵字2bytes,一個關鍵字具體資訊指標2bytes。一棵9階B-tree(一個結點最多8個關鍵字)的內部結點需要2個盤快。而B+ 樹內部結點只需要1個盤快。當需要把內部結點讀入記憶體中的時候,B 樹就比B+ 樹多一次盤塊查詢時間(在磁碟中就是碟片旋轉的時間)。

(2)B+tree的查詢效率更加穩定
由於非葉子結點並不是最終指向檔案內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查詢必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當。

(3)B樹在提高了磁碟IO效能的同時並沒有解決元素遍歷的效率低下的問題。正是為了解決這個問題,B+樹應運而生。B+樹只要遍歷葉子節點就可以實現整棵樹的遍歷。而且在資料庫中基於範圍的查詢是非常頻繁的,而B樹不支援這樣的操作(或者說效率太低)。

LSM樹

目前常見的主要的三種儲存引擎是:雜湊、B+樹、LSM樹:

  • 雜湊儲存引擎:是雜湊表的持久化實現,支援增、刪、改以及隨機讀取操作,但不支援順序掃描,對應的儲存系統為key-value儲存系統。對於key-value的插入以及查詢,雜湊表的複雜度都是O(1),明顯比樹的操作O(n)快,如果不需要有序的遍歷資料,雜湊表效能最好。
  • B+樹儲存引擎是B+樹的持久化實現,不僅支援單條記錄的增、刪、讀、改操作,還支援順序掃描(B+樹的葉子節點之間的指標),對應的儲存系統就是關係資料庫(Mysql等)。
  • LSM樹(Log-Structured MergeTree)儲存引擎和B+樹儲存引擎一樣,同樣支援增、刪、讀、改、順序掃描操作。而且通過批量儲存技術規避磁碟隨機寫入問題。當然凡事有利有弊,LSM樹和B+樹相比,LSM樹犧牲了部分讀效能,用來大幅提高寫效能。

上面三種引擎中,LSM樹儲存引擎的代表資料庫就是HBase.

LSM樹核心思想的核心就是放棄部分讀能力,換取寫入的最大化能力。LSM Tree ,這個概念就是結構化合並樹的意思,它的核心思路其實非常簡單,就是假定記憶體足夠大,因此不需要每次有資料更新就必須將資料寫入到磁碟中,而可以先將最新的資料駐留在記憶體中,等到積累到足夠多之後,再使用歸併排序的方式將記憶體內的資料合併追加到磁碟隊尾(因為所有待排序的樹都是有序的,可以通過合併排序的方式快速合併到一起)。

日誌結構的合併樹(LSM-tree)是一種基於硬碟的資料結構,與B+tree相比,能顯著地減少硬碟磁碟臂的開銷,並能在較長的時間提供對檔案的高速插入(刪除)。然而LSM-tree在某些情況下,特別是在查詢需要快速響應時效能不佳。通常LSM-tree適用於索引插入比檢索更頻繁的應用系統。

LSM樹和B+樹的差異主要在於讀效能和寫效能進行權衡。在犧牲的同時尋找其餘補救方案:

(a)LSM具有批量特性,儲存延遲。當寫讀比例很大的時候(寫比讀多),LSM樹相比於B樹有更好的效能。因為隨著insert操作,為了維護B+樹結構,節點分裂。讀磁碟的隨機讀寫概率會變大,效能會逐漸減弱。

(b)B樹的寫入過程:對B樹的寫入過程是一次原位寫入的過程,主要分為兩個部分,首先是查詢到對應的塊的位置,然後將新資料寫入到剛才查詢到的資料塊中,然後再查詢到塊所對應的磁碟物理位置,將資料寫入去。當然,在記憶體比較充足的時候,因為B樹的一部分可以被快取在記憶體中,所以查詢塊的過程有一定概率可以在記憶體內完成,不過為了表述清晰,我們就假定記憶體很小,只夠存一個B樹塊大小的資料吧。可以看到,在上面的模式中,需要兩次隨機尋道(一次查詢,一次原位寫),才能夠完成一次資料的寫入,代價還是很高的。

(c)LSM優化方式

  1. Bloom filter: 就是個帶隨機概率的bitmap,可以快速的告訴你,某一個小的有序結構裡有沒有指定的那個資料的。於是就可以不用二分查詢,而只需簡單的計算幾次就能知道資料是否在某個小集合裡啦。效率得到了提升,但付出的是空間代價。
  2. compact:小樹合併為大樹:因為小樹效能有問題,所以要有個程序不斷地將小樹合併到大樹上,這樣大部分的老資料查詢也可以直接使用log2N的方式找到,不需要再進行(N/m)*log2n的查詢了

Hbase中儲存設計主要思想

SML樹原理把一棵大樹拆分成N棵小樹,它首先寫入記憶體中,隨著小樹越來越大,記憶體中的小樹會flush到磁碟中,磁碟中的樹定期可以做merge操作,合併成一棵大樹,以優化讀效能。

這裡寫圖片描述

以上這些大概就是HBase儲存的設計主要思想,這裡分別對應說明下:

  • 因為小樹先寫到記憶體中,為了防止記憶體資料丟失,寫記憶體的同時需要暫時持久化到磁碟,對應了HBase的MemStore和HLog
  • MemStore上的樹達到一定大小之後,需要flush到HRegion磁碟中(一般是Hadoop DataNode),這樣MemStore就變成了DataNode上的磁碟檔案StoreFile,定期HRegionServer對DataNode的資料做merge操作,徹底刪除無效空間,多棵小樹在這個時機合併成大樹,來增強讀效能。