數據的存儲結構淺析LSM-Tree和B-tree


本篇主要討論的是不一樣存儲結構(主要是LSM-tree和B-tree),它們應對的不一樣場景,所採用的底層存儲結構,以及對應用以提高效率的索引。

所謂數據庫,最基礎的功能,就是保存數據,而且在須要的時候能夠方便地檢索到須要的數據。在這個基礎上,演化出了不一樣的數據庫系統,以及多種索引機制幫助檢索數據。這篇咱們就來討論幾種常見的數據存儲和索引機制,主要是B-tree,LSM-Tree,以及它們對應的優缺點。java

順序存儲與哈希索引

試想一下,若是按照保存數據,而且在須要的時候能夠方便地檢索到須要的數據這一標準,設計一個簡單的數據庫,那麼最簡單的作法應該怎麼作呢?mysql

最簡單的作法,就是經過順序保存數據到一個日誌文件中。而後經過索引,這裏以哈希索引爲例(好比java的hashMap),記錄每條數據的key以及對應的位移,將其保存到內存中,避免隨機檢索巨大的開銷。值得注意的是,引入索引,雖然會顯著提升查詢效率,但會略微下降寫入速度。由於每次寫入的時候都須要額外寫入到哈希索引中,這一點對大部分索引都是適用的。算法

哈希索引

上圖爲哈希索引示例,下面是順序存儲在磁盤上是日誌數據,上面的內存中的哈希索引。哈希索引是不少複雜索引的基礎,好比在mysql中就有提供哈希索引的選項,固然哈希索引並不經常使用,由於它最基礎,同時也意味着它最容易被優化。sql

上述形式的順序存儲+哈希索引中,增長數據和查找數據相對容易理解,而修改數據則能夠經過將新數據追加到文件尾部,從新生成索引實現,刪除操做則能夠給與哈希索引一個標識符實現(如對應key置爲-1)。數據庫

但這樣有一個問題,可能會出現磁盤耗盡的狀況。針對這一個問題,咱們能夠將日誌文件拆分紅多個必定大小的文件段(這裏的文件段能夠理解爲接受統一管理的數據文件)。當一個文件段達到必定大小,好比4kb的時候,就關閉它,新建一個文件段。而舊的文件段能夠進行壓縮,前面提到過,刪除和修改都是經過追加日誌相同的key-value實現的,那麼早先的數據其實就已經沒用的,因此壓縮的時候只保留最新的key數據。壓縮到過程以下面這張圖所示:數據結構

壓縮數據

圖中上面的部分就是順序存儲的數據,能夠發現其中有不少的key都是相同的,這是由於順序存儲狀況下,修改數據就是不斷新寫入相同的key。這種狀況咱們要的只有相同key的最新的value。因此壓縮過程也是一個清理磁盤的過程。分佈式

壓縮合並過程能夠由後臺進行默默進行,因此沒必要擔憂這個過程影響查詢性能。上圖中只有一個數據文件段,但實際上能夠有多個文件段,多個文件段也能夠合併(相似於Hbase中多個文件的merge操做)。oop

固然這樣的優化能夠極大程度節省空間,但必不可少得會給檢索帶來時間上的損耗。在多個文件段的狀況,每一個文件段都有本身的哈希索引,故而要查找數據會首先根據key查找內存中最新文件段的哈希索引,若是找不到,那麼找次新文件段的哈希索引,接着找次新的哈希索引,直到遍歷全部文件段的哈希索引。性能

綜上,順序存儲+哈希索引優勢明顯,簡單,高效。缺點是哈希索引需所有存到內存(若是將哈希索引放到磁盤那至關於放棄了檢索的高效),而且難以實現區域查詢優化

爲了解決它的這些問題,咱們能夠將哈希索引作一些小小的改變。具體來講,就是讓文件段的數據,按key進行排序存儲。這樣會帶來哪些改變呢?

SSTable和LSM tree

將數據文件段中的數據按key進行排序,而且保證相同的key只出現一次(在壓縮的時候保證),這種格式就稱之爲排序字符串表,簡稱SStable(Sorted String Table)。

將數據按Key進行排序後有如下幾個好處:

  1. 合併更加簡單高效,即便數據文件段大於內存,也可使用相似歸併排序算法進行數據段的壓縮,即將一個大文件拆成多個小數據進行壓縮。若是多個文件段中有相同的key,那麼以最新的文件段的key爲準。
  2. 緩解哈希索引須要整個hashMap存儲到內存的窘境。由於key是排序的,因此能夠在內存中維持一個稀疏索引,存儲每一個key的範圍,具體見下圖。而且這個稀疏索引所需的內存空間是很小的。

稀疏索引與SStable

經過稍微改變一下文件段的結果,就得到如此多的好處。但還有一個問題,前面的哈希索引是基於順序存儲的日誌文件的,要讓SStable按key排序,那就不能順序存儲磁盤了呀(即沒法存儲的時候當即寫入磁盤)!!的確是這樣,雖然也可使用相似B-tree來實現磁盤上的排序存儲,但轉換下思路,其實將數據先保存在內存中其實更加方便

具體實現流程,是在內存中維護一個相似TreeMap的數據結構用於存儲數據(TreeMap底層是基於紅黑樹對存儲的key進行排序的。不管咱們按照什麼樣的順序存儲數據,TreeMap老是會將數據按照key進行排序)。這個TreeMap稱爲內存表,當內存表超過必定閾值的時候,就將其寫入到磁盤中,成爲SStable,由於已經排好序,因此寫入的效率其實比想象的要高。後期再對磁盤中的SStable進行壓縮與合併操做。

當須要根據key檢索的時候,會先去內存表中檢索,找不到再去最新的SStable,再去次新的SStable,直到遍歷徹底部。

上述這種索引結構被稱爲之LSM-Tree,全稱是Log-Structured Merge-Tree,即日誌合併樹。而這種基於合併和壓縮文件原理的存儲引擎被稱爲LSM存儲引擎,其中比較爲人所知的是Hbase。

B-Tree

最後,咱們再來討論流傳最久的數據庫村粗結構。與LSM-Tree這幾年才逐漸爲人所知不一樣,B-tree存儲結構擔得起經久不衰這四個字。

B-tree自己是一種樹形的數據結構,更具體點說是一顆平衡查找樹,它也是經過存儲順序的key存儲數據(這一點和SStable有類似之處)。不一樣於前面的LSM-tree的文件段,B-tree將數據庫分解成固定大小的塊或頁,一般一個頁大小是4kb。這種分配方法更加貼合底層的磁盤。

當須要進行查找的時候,老是從根開始,根據範圍跳轉到對應的key,而其對應的value能夠是值自己,也能夠是指向存儲對應數據的磁盤地址。下圖是一個具體的例子:

B-tree

而在更新或插入的時候,有可能會出現沒有足夠空間來容納新key的問題,這時候就會發生分裂。分裂操做是比較危險的,在分裂的時候若是數據庫崩潰,可能會致使索引被破壞。爲了防止這個問題,能夠引入預寫日誌(write-ahead log,WAL)機制。mysql的binlog就是這樣的東西。具體說就是在執行操做的時候,將這次操做寫入一個只容許追加的文件中,這樣一來當崩潰的時候就能夠檢查日誌並進行恢復。

存儲結構的比對

從使用的角度上來講,B-tree等索引存儲結構多用於OLTP型的數據庫,由於這類數據庫主要以事務,或是行級別的讀取和存儲爲主的(好比Mysql)。換句話說,這種類型的數據庫更多的操做是小批量或單行級別的更新或讀取,而且可能還有事務方面的需求,這種類型正是B-tree結構所擅長的。

而 LSM-tree則多用於大規模數據狀況下的檢索分析和快速寫入的狀況。在寫入的性能上,由於上直接寫入內存再按期刷入到磁盤中,因此寫入操做對用戶的感知而言上很是迅速的。而檢索速度也由於key順序存儲,能夠快速定位到key對應的位置,於是具備較好的檢索性能。

可是LSM-tree比較顯著的應用方向仍是在大規模分析這方面,在大規模分析(OLAP)場景下,數據一般都是列式存儲,而且須要全表掃描。其中磁盤數據可使用二進制進行壓縮,讀取的時候能夠有效減小磁盤IO的處理時間(與之相比,B-tree等存儲結構就沒法充分壓縮,由於每次都只處理小部分數據)。同時在存儲文件中還能再進一步切分,好比將列式數據按照水平切分紅不一樣的Page,同時存儲一些簡單的索引,用來指定不一樣Page大概範圍,Hadoop的存儲數據格式Parquet就是相似的設計。

小結

本篇主要討論了幾種基礎的存儲結構和索引以及其對應使用場景,限於篇幅,更多索引的變種沒法多加討論,好比B-tree的優化版B+tree,多列索引等。

其實大部分數據庫或者說存儲引擎,都是針對不一樣的場景下,在舊有的基礎上進行必定程度的微改造創新,但大致的結構依舊是以上述兩三種爲準,瞭解了上述幾種結構,對數據存儲方面應該可以有一個感性的認知了。

此外本章多參考自《DDIA》第三章節,對分佈式系統感興趣的童鞋能夠看看此書,確定不會失望的。

以上~

相關文章
相關標籤/搜索