索引四 LSM樹

一 背景

本來想寫點B+樹的,不過B+樹因為用在Mysql等關系型數據庫中,大家都比較了解了,而LSM樹這種索引設計思路主要用在NoSql中,如果沒有接觸過NoSQL數據庫的朋友可能了解不多,就開一篇介紹下,參考了不少的文章和資料。

LSM樹是Log Structured Merge Trees的簡稱(這里面的日志,不一定是指我們程序的日志,也是指一類以時間為其中維度的大批量的樹)。在NoSQL數據庫中有廣泛的應用(比如LevelDB和HBASE等)。嚴格來講并不是一種索引結構,是一種索引設計的整體架構性的東西。B+樹作為關系型數據庫常用的索引存儲結構,本身挺優秀的,支持范圍查詢,隨機訪問,增刪改查都支持的不錯,只是如果在修改的時候,B+樹的聚簇索引數據是存在葉子節點的,需要磁盤的隨機讀寫,隨機寫的性能不夠好。磁盤的隨機讀寫的性能不好,順序讀寫的性能可以達到和內存一個數量級別,對此的優化很容易想到采用批量順序寫替代磁盤的隨機寫。

LSM樹適合的場景是寫入的量很大,查詢的量比較小,而且查詢查詢近期的數據,老的數據一般很少查詢的場景,比較適合用LSM樹。

二 LSM樹的關鍵思想

Log Structured 采用日志追加的方式,而不是采用隨機寫,追加采用順序寫,所以性能好。另一個重要的思想是Merge Trees , 數據寫入的時候,先寫入到內存的樹中,可以是紅黑樹,也可能是B+樹,有的采用跳表的內存數據結構。
當內存中的樹大小達到一定規模后,將內存中的樹和磁盤中的樹進行合并,這就是合并樹的來源。數據開始寫入到內存的時候,如果突然斷電,或者系統異常,會導致數據丟失,為了防止數據丟失,采用WAL(Write Ahead Log 預寫日志技術)將數據第一時間寫入到磁盤文件中,由于是順序寫,所以性能上不用擔心。在發生異常的時候,可以通過WAL文件進行數據恢復。

RocksDB中的LSM組成

如上圖所示,LSM樹,整體由內存部分數據結構和磁盤上的兩類文件組成。
內存中的數據結構為memtable和immutable Memtable兩部分組成,前一個可讀可寫,后一個是只讀的,它們兩是一樣的數據結構。immutable Memtable 由于是只讀的,可以在刷入到磁盤的時候不用加鎖,提升性能。那么數據是如何讀寫的那?

寫數據

  1. 寫請求來之后,寫入到WAL中和內存的memtable中,memtable可以是紅黑樹或跳表等。
  2. 如果memtable滿了,就新申請內存構建一個新的memtable,且將原來的memtable轉為只讀的immutable memtable,適當時機刷到磁盤中,保存為一個SStable文件。
  3. 刷新到磁盤之后,就可以對WAL日志進行截斷,這樣防止WAL日志的無限擴大的問題。
  4. immutable memtable 保存到磁盤中,如果直接和磁盤上的文件進行合并,因為內存中的數據少,磁盤中的很大,合并的數據很少,卻占用了大量的IO磁盤,這肯定不行。所以像LevelDB采用延遲合并的方式,每層滿了之后,都會和下一層進行滾動合并。

讀數據

  1. 讀數據的時候,先從內存中的memtable中讀取。
  2. 如果沒有,則到immutable memtable中讀取。
  3. 如果仍然未查詢到數據,則到緩存中讀取,緩存里面包括數據緩存和索引緩存。
  4. 如果仍然沒有找到,則從Level 0層開始查找,由于Level 0層的SSTable沒有合并,所以數據是有重合的,所以每個SSTable都需要查找,Level 0層的只有4MB(level DB)中,所以也比較快。
  5. 如果Level 0 層,沒有找到數據,則下沉到下一層Level 1層繼續查找,一層以及以后層次SSTABLE文件是不重合的,所以可以通過快速定位到SSTable文件中,進行查找,找到了或所有的層都查找完畢后返回。

三 SSTable

SSTableSorted String Table ,聽著蠻高大上,其實就是排序的有序鍵值對集合,是存儲在文件中的結構。
我們保存哈希表為磁盤文件中,為了提升查詢速度,一般還保存一個索引文件,保存key和offset的位置,通過key快速獲得offset,再打開文件,直接定位到offset位置。

哈希表持久化

但是哈希表沒有順序的,訪問不同的key時候需要隨機的磁盤訪問,而且哈希表沖突的時候,需要復雜的處理邏輯,還無法支持范圍查詢。

簡單改變下,我們按照key-value對的順序按鍵排序,這種格式稱為排序字符串表即SSTable。


LevelDB 的SSTable存儲格式

此結構將數據部分和索引部分分離,在查詢的時候,不需要將整個SSTable文件都讀入到內存中。而是先讀入索引部分,可以通過布隆過濾,快速判斷要查的key是否存在,如果不存在,就不用再讀數據部分了。

索引部分的Index Block記錄采用key:offset:size形式,key為每個Data Block 最小的key,offset記錄Data Block的起始位置,size為每個Data Block的大小。每個Data Block采用順序存儲的方式,我們可以方便進行二分查找。

四 索引加速

如果每次都從 SSTable 中加載數據都需要從磁盤讀取數據,性能比較差。所以LevelDB中設計了內存的緩存區。緩存區包括兩個部分,一部分為最近使用的Index Block,另一個部分為Data Block,這樣如果我們搜索的索引和數據都在內存中,就不用從磁盤中讀取,提升了查詢性能。

五 Level中SSTable合并

首先為什么需要SSTable合并那,那是因為,隨著數據的增大,寫的SSTable文件越來越多,而且隨著對key的更新和刪除,這種需要刪除的數據越來越多,為了減少文件數量和清理無效數據,就需要進行compact,即將多個SSTable文件合并成一個SSTable文件。

SSTable 按照分層滾動合并的方式,Level DBLevel 0層最多只能保存4個SSTable,當Level 0層滿了之后,我們就將它們進行多路歸并,合并后的文件就是Level 1層的有序SSTable文件,

Level 0 層,SSTable層的key的范圍是不交叉的,這樣查詢的時候,就可以通過二分查找的方式進行快速查找了。Level 1 層的SSTable 就會從Level 1 層中輪詢的方式選擇一個SSTable去和Level 2 層在此SSTablekey 范圍內的SSTable進行文件合并,為了防止合并的占用的IO比較多,所以在生成SSTable的會判斷此文件和下一層多少個SSTablekey的重合,如果超過10個就停止寫入,生成新的SSTable 文件。這樣每次合并SSTable文件消耗的IO也不至于太多。

SSTable合并

合并的過程中,老的SSTable文件,是不能刪除的,所以就會同時存在老的SSTable文件和新的SSTable文件,導致同一份數據因為被compaction,數據最多可能膨脹到原來的2倍。

不過數據會膨脹,由于在compaction過程中,有可能同一份數據不斷隨著compaction過程向更高層重復寫入,有多少層有可能就寫入多少次,IO幾倍量的增加。

六 參考

《核心索引20講》 陳東
《數據密集型應用系統設計》 Martin Leppmann

七 詩詞欣賞

我寧愿瞄準星星,
卻擊不中他們;
也不愿沒有目標;
我寧愿去追逐目標,
卻得不到它,
也不愿不曾追逐,
我寧愿去嘗試卻失敗,
也不愿不曾嘗試。
我不想活著的每一天
都在幻想如果我當初付出了更多努力
現在會怎樣?
我會去放手追逐、
無論刀山火海,
我會去追逐我的命運!
你不能朝著你的夢想漫步,
你不能朝著夢想行走,
你要向它狂奔!
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 在前面我寫了B、B+樹、Wisckey的總結。不過我覺得應該將今天的內容放在總結Wisckey之前。因為wisck...
    CPinging閱讀 3,104評論 0 0
  • 最近練手的項目里用到了LevelDB, 具有很優秀的存儲效率,DDIA中有介紹它底層是LSM-tree實現的,今天...
    芥川世之介閱讀 755評論 0 0
  • 前言 LSM樹全稱為The Log structured Merage - Tree,根據名稱可以大概認識到主要有...
    數據100閱讀 1,770評論 0 0
  • LSM-Tree是什么 LSM-Tree(Log Structured Merge Tree),一種分層、有序、面...
    雁陣驚寒_zhn閱讀 1,431評論 0 2
  • 漸變的面目拼圖要我怎么拼? 我是疲乏了還是投降了? 不是不允許自己墜落, 我沒有滴水不進的保護膜。 就是害怕變得面...
    悶熱當乘涼閱讀 4,324評論 0 13