原文鏈接:https://blog.csdn.net/u010853261/article/details/78217823
前言
動態查找樹主要有:二叉查找樹、平衡二叉樹、紅黑樹、B樹、B+樹。前面三種是典型的二叉查找樹,查找的時間復雜度是O(log2N)與樹的深度有關系,那么降低樹的深度也就可以提升查找效率。這時就提出了平衡多路查找樹,也就是B樹以及B+樹。
B樹和B+樹非常典型的場景就是用于關系型數據庫的索引(MySQL)
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樹的特性:
關鍵字集合分布在整顆樹中;
任何一個關鍵字出現且只出現在一個結點中;
搜索有可能在非葉子結點結束(樹中所有結點都存儲數據,與B+樹這一點不同);
其搜索性能等價于在關鍵字全集內做一次二分查找;
下面是一個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樹的差異在于:
有n棵子樹的結點中含有n個關鍵字,每個關鍵字不保存數據,只用來索引,所有數據都保存在葉子節點。
所有的葉子結點中包含了全部關鍵字的信息,及指向含這些關鍵字記錄的指針,且葉子結點本身依關鍵字的大小自小而大順序鏈接。
所有的非終端結點可以看成是索引部分,結點中僅含其子樹(根結點)中的最大(或最小)關鍵字。
為所有葉子結點增加一個鏈指針,便于區間查找和遍歷。
所有關鍵字都在葉子結點出現;
如下圖一個M=3 的B+樹:
B+樹的搜索:與B-樹也基本相同,區別是B+樹只有達到葉子結點才命中(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樹不支持這樣的操作(或者說效率太低)。
目前常見的主要的三種存儲引擎是:哈希、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優化方式:
Bloom filter: 就是個帶隨機概率的bitmap,可以快速的告訴你,某一個小的有序結構里有沒有指定的那個數據的。于是就可以不用二分查找,而只需簡單的計算幾次就能知道數據是否在某個小集合里啦。效率得到了提升,但付出的是空間代價。
compact:小樹合并為大樹:因為小樹性能有問題,所以要有個進程不斷地將小樹合并到大樹上,這樣大部分的老數據查詢也可以直接使用log2N的方式找到,不需要再進行(N/m)*log2n的查詢了
SML樹原理把一棵大樹拆分成N棵小樹,它首先寫入內存中,隨著小樹越來越大,內存中的小樹會flush到磁盤中,磁盤中的樹定期可以做merge操作,合并成一棵大樹,以優化讀性能。
以上這些大概就是HBase存儲的設計主要思想,這里分別對應說明下:
因為小樹先寫到內存中,為了防止內存數據丟失,寫內存的同時需要暫時持久化到磁盤,對應了HBase的MemStore和HLog
MemStore上的樹達到一定大小之后,需要flush到HRegion磁盤中(一般是Hadoop DataNode),這樣MemStore就變成了DataNode上的磁盤文件StoreFile,定期HRegionServer對DataNode的數據做merge操作,徹底刪除無效空間,多棵小樹在這個時機合并成大樹,來增強讀性能。