看圖輕鬆理解數據結構與算法系列(NoSQL存儲-LSM樹)

前言

推出一個新系列,《看圖輕鬆理解數據結構和算法》,主要使用圖片來描述常見的數據結構和算法,輕鬆閱讀並理解掌握。本系列包括各類堆、各類隊列、各類列表、各類樹、各類圖、各類排序等等幾十篇的樣子。mysql

關於LSM樹

LSM樹,即日誌結構合併樹(Log-Structured Merge-Tree)。其實它並不屬於一個具體的數據結構,它更可能是一種數據結構的設計思想。大多NoSQL數據庫核心思想都是基於LSM來作的,只是具體的實現不一樣。因此原本不打算列入該系列,可是有朋友留言了好幾回讓我講LSM樹,那麼就說一下LSM樹。算法

LSM樹誕生背景

傳統關係型數據庫使用btree或一些變體做爲存儲結構,能高效進行查找。但保存在磁盤中時它也有一個明顯的缺陷,那就是邏輯上相離很近但物理卻可能相隔很遠,這就可能形成大量的磁盤隨機讀寫。隨機讀寫比順序讀寫慢不少,爲了提高IO性能,咱們須要一種能將隨機操做變爲順序操做的機制,因而便有了LSM樹。LSM樹能讓咱們進行順序寫磁盤,從而大幅提高寫操做,做爲代價的是犧牲了一些讀性能。sql

關於磁盤IO

磁盤讀寫時涉及到磁盤上數據查找,地址通常由柱面號、盤面號和塊號三者構成。也就是說移動臂先根據柱面號移動到指定柱面,而後根據盤面號肯定盤面的磁道,最後根據塊號將指定的磁道段移動到磁頭下,即可開始讀寫。數據庫

整個過程主要有三部分時間消耗,查找時間(seek time) +等待時間(latency time)+傳輸時間(transmission time) 。分別表示定位柱面的耗時、將塊號指定磁道段移到磁頭的耗時、將數據傳到內存的耗時。整個磁盤IO最耗時的地方在查找時間,因此減小查找時間能大幅提高性能。網絡

LSM樹原理

LSM樹由兩個或以上的存儲結構組成,好比在論文中爲了方便說明使用了最簡單的兩個存儲結構。一個存儲結構常駐內存中,稱爲C0 tree,具體能夠是任何方便健值查找的數據結構,好比紅黑樹、map之類,甚至能夠是跳錶。另一個存儲結構常駐在硬盤中,稱爲C1 tree,具體結構相似B樹。C1全部節點都是100%滿的,節點的大小爲磁盤塊大小。數據結構

image

插入步驟

大致思路是:插入一條新紀錄時,首先在日誌文件中插入操做日誌,以便後面恢復使用,日誌是以append形式插入,因此速度很是快;將新紀錄的索引插入到C0中,這裏在內存中完成,不涉及磁盤IO操做;當C0大小達到某一閾值時或者每隔一段時間,將C0中記錄滾動合併到磁盤C1中;對於多個存儲結構的狀況,當C1體量愈來愈大就向C2合併,以此類推,一直往上合併Ck。併發

image

合併步驟

合併過程當中會使用兩個塊:emptying block和filling block。app

  1. 從C1中讀取未合併葉子節點,放置內存中的emptying block中。
  2. 從小到大找C0中的節點,與emptying block進行合併排序,合併結果保存到filling block中,並將C0對應的節點刪除。
  3. 不斷執行第2步操做,合併排序結果不斷填入filling block中,當其滿了則將其追加到磁盤的新位置上,注意是追加而不是改變原來的節點。合併期間如故宮emptying block使用完了則再從C1中讀取未合併的葉子節點。
  4. C0和C1全部葉子節點都按以上合併完成後即完成一次合併。

關於優化措施

本文用圖闡述LSM的基本原理,但實際項目中其實有不少優化策略,並且有不少針對LSM樹優化的paper。好比使用布隆過濾器快速判斷key是否存在,還有作一些額外的索引以幫助更快找到記錄等等。機器學習

插入操做

向LSM樹中插入A E L R U,首先會插入到內存中的C0樹上,這裏使用AVL樹,插入「A」,先項磁盤日誌文件追加記錄,而後再插入C0,異步

image

插入「E」,一樣先追加日誌再寫內存,

image

繼續插入「L」,旋轉後以下,

image

插入「R」「U」,旋轉後最終以下。

image

假設此時觸發合併,則由於C1尚未樹,因此emptying block爲空,直接從C0樹中依次找最小的節點。filling block長度爲4,這裏假設磁盤塊大小爲4。

開始找最小的節點,並放到filling block中,

image

繼續找第二個節點,

image

以此類推,填滿filling block,

image

開始寫入磁盤,C1樹,

image

繼續插入B F N T,先分別寫日誌,而後插入到內存的C0樹中,

image

假如此時進行合併,先加載C1的最左邊葉子節點到emptying block,

image

接着對C0樹的節點和emptying block進行合併排序,首先是「A」進入filling block,

image

而後是「B」,

image

合併排序最終結果爲,

image

將filling block追加到磁盤的新位置,將原來的節點刪除掉,

image

繼續合併排序,再次填滿filling block,

image

將filling block追加到磁盤的新位置,上一層的節點也要以磁盤塊(或多個磁盤塊)大小寫入,儘可能避開隨機寫。另外因爲合併過程可能會致使上層節點的更新,能夠暫時保存在內存,後面在適當時機寫入。

image

查找操做

查找整體思想是先找內存的C0樹,找不到則找磁盤的C1樹,而後是C2樹,以此類推。

假如要找「B」,先找C0樹,沒找到。

image

接着找C1樹,從根節點開始,

image

找到「B」。

image

刪除操做

刪除操做爲了能快速執行,主要是經過標記來實現,在內存中將要刪除的記錄標記一下,後面異步執行合併時將相應記錄刪除。

好比要刪除「U」,假設標爲#的表示刪除,則C0樹的「U」節點變爲,

image

而若是C0樹不存在的記錄,則在C0樹中生成一個節點,並標爲#,查找時就能再內存中得知該記錄已被刪除,無需去磁盤找了。好比要刪除「B」,那麼沒有必要去磁盤執行刪除操做,直接在C0樹中插入一個「B」節點,並標爲#。

image

-------------推薦閱讀------------

個人開源項目彙總(機器&深度學習、NLP、網絡IO、AIML、mysql協議、chatbot)

爲何寫《Tomcat內核設計剖析》

個人2017文章彙總——機器學習篇

個人2017文章彙總——Java及中間件

個人2017文章彙總——深度學習篇

個人2017文章彙總——JDK源碼篇

個人2017文章彙總——天然語言處理篇

個人2017文章彙總——Java併發篇


跟我交流,向我提問:

歡迎關注:

相關文章
相關標籤/搜索