[LevelDB] 0.1 LSMTree介紹

1、存儲引擎介紹html

講LSM樹以前,須要提下三種基本的存儲引擎,這樣才能清楚LSM樹的由來mysql

1.1 哈希存儲引擎  git

  是哈希表的持久化實現,支持增、刪、改以及隨機讀取操做,但不支持順序掃描,對應的存儲系統爲key-value存儲系統。對於key-value的插入以及查詢,哈希表的複雜度都是O(1),明顯比樹的操做O(n)快,若是不須要有序的遍歷數據,哈希表就是your Mr.Rightgithub

表明數據庫:redis、memcache等redis

一般也常見於其餘存儲引擎的查找速度優化上。 Hash 索引結構的特殊性,其檢索效率很是高,索引的檢索能夠一次定位,不像B-Tree 索引須要從根節點到枝節點,最後才能訪問到頁節點這樣屢次的IO訪問,因此 Hash 索引的查詢效率要遠高於 B-Tree 索引。雖然 Hash 索引效率高,可是 Hash 索引自己因爲其特殊性也帶來了不少限制和弊端。算法

這裏列舉缺點:sql

(1)Hash 索引僅僅能知足"=","IN"和"<=>"查詢,不能使用範圍查詢。
(2)Hash 索引沒法被用來避免數據的排序操做。
(3)Hash 索引不能利用部分索引鍵查詢。
(4)Hash 索引在任什麼時候候都不能避免表掃描。數據庫

1.2 B樹存儲引擎緩存

  B樹(關於B樹的由來,數據結構以及應用場景能夠看以前一篇博文)的持久化實現,不只支持單條記錄的增、刪、讀、改操做,還支持順序掃描(B+樹的葉子節點之間的指針),對應的存儲系統就是關係數據庫(Mysql等)。數據結構

表明數據庫:MongoDB、mysql(基本上關係型數據庫)等

 

1.3 LSM樹(Log-Structured Merge Tree)存儲引擎

  B樹存儲引擎同樣,一樣支持增、刪、讀、改、順序掃描操做。並且經過批量存儲技術規避磁盤隨機寫入問題。固然凡事有利有弊,LSM樹和B+樹相比,LSM樹犧牲了部分讀性能,用來大幅提升寫性能。

LSM被設計來提供比傳統的B+樹或者ISAM更好的寫操做吞吐量,經過消去隨機的本地更新操做來達到這個目標。

那麼爲何這是一個好的方法呢?這個問題的本質仍是磁盤隨機操做慢,順序讀寫快的老問題。這二種操做存在巨大的差距,不管是磁盤仍是SSD。

thoughput of two op

上圖很好的說明了這一點,他們展示了一些反直覺的事實,順序讀寫磁盤(無論是SATA仍是SSD)快於隨機讀寫主存,並且快至少三個數量級。這說明咱們要避免隨機讀寫,最好設計成順序讀寫。

The Base LSM Algorithm

從概念上說,最基本的LSM是很簡單的 。將以前使用一個大的查找結構(形成隨機讀寫,影響寫性能),變換爲將寫操做順序的保存到一些類似的有序文件(也就是sstable)中。因此每一個文件包 含短期內的一些改動。由於文件是有序的,因此以後查找也會很快。文件是不可修改的,他們永遠不會被更新,新的更新操做只會寫到新的文件中。讀操做檢查很 有的文件。經過週期性的合併這些文件來減小文件個數。

basic lsm
讓咱們更具體的看看,當一些更新操做到達時,他們會被寫到內存緩存(也就是memtable)中,memtable使用樹結構來保持key的有序,在大部 分的實現中,memtable會經過寫WAL的方式備份到磁盤,用來恢復數據,防止數據丟失。當memtable數據達到必定規模時會被刷新到磁盤上的一 個新文件,重要的是系統只作了順序磁盤讀寫,由於沒有文件被編輯,新的內容或者修改只用簡單的生成新的文件。

因此越多的數據存儲到系統中,就會有越多的不可修改的,順序的sstable文件被建立,它們表明了小的,按時間順序的修改。

由於比較舊的文件不會被更新,重複的紀錄只會經過建立新的紀錄來覆蓋,這也就產生了一些冗餘的數據。

因此係統會週期的執行合併操做(compaction)。 合併操做選擇一些文件,並把他們合併到一塊兒,移除重複的更新或者刪除紀錄,同時也會刪除上述的冗餘。更重要的是,經過減小文件個數的增加,保證讀操做的性 能。由於sstable文件都是有序結構的,因此合併操做也是很是高效的。

當一個讀操做請求時,系統首先檢查內存數據(memtable),若是沒有找到這個key,就會逆序的一個一個檢查sstable文件,直到key 被找到。由於每一個sstable都是有序的,因此查找比較高效(O(logN)),可是讀操做會變的愈來愈慢隨着sstable的個數增長,由於每個 sstable都要被檢查。(O(K log N), K爲sstable個數, N 爲sstable平均大小)。

因此,讀操做比其它本地更新的結構慢,幸運的是,有一些技巧能夠提升性能。最基本的的方法就是頁緩存(也就是leveldb的 TableCache,將sstable按照LRU緩存在內存中)在內存中,減小二分查找的消耗。LevelDB 和 BigTable 是將 block-index 保存在文件尾部,這樣查找就只要一次IO操做,若是block-index在內存中。一些其它的系統則實現了更復雜的索引方法。

 

即便有每一個文件的索引,隨着文件個數增多,讀操做仍然很慢。經過週期的合併文件,來保持文件的個數,因些讀操做的性能在可接收的範圍內。即使有了合 並操做,讀操做仍然會訪問大量的文件,大部分的實現經過布隆過濾器來避免大量的讀文件操做,布隆過濾器是一種高效的方法來判斷一個sstable中是否包 含一個特定的key。(若是bloom說一個key不存在,就必定不存在,而當bloom說一個文件存在是,多是不存在的,只是經過幾率來保證)

全部的寫操做都被分批處理,只寫到順序塊上。另外,合併操做的週期操做會對IO有影響,讀操做有可能會訪問大量的文件(散亂的讀)。這簡化了算法工 做的方法,咱們交換了讀和寫的隨機IO。這種折衷頗有意義,咱們能夠經過軟件實現的技巧像布隆過濾器或者硬件(大文件cache)來優化讀性能。
read

Basic Compaction

爲了保持LSM的讀操做相對較快,維護並減小sstable文件的個數是很重要的,因此讓咱們更深刻的看一下合併操做。這個過程有一點兒像通常垃圾回收算法。

當必定數量的sstable文件被建立,例若有5個sstable,每個有10行,他們被合併爲一個50行的文件(或者更少的行數)。這個過程一 直持續着,當更多的有10行的sstable文件被建立,當產生5個文件時,它們就被合併到50行的文件。最終會有5個50行的文件,這時會將這5個50 行的文件合併成一個250行的文件。這個過程不停的建立更大的文件。像下圖:
compaction

上述的方案有一個問題,就是大量的文件被建立,在最壞的狀況下,全部的文件都要搜索。

Levelled Compaction

更新的實現,像 LevelDB 和 Cassandra解決這個問題的方法是:實現了一個分層的,而不是根據文件大小來執行合併操做。這個方法能夠減小在最壞狀況下須要檢索的文件個數,同時也減小了一次合併操做的影響。

按層合併的策略相對於上述的按文件大小合併的策略有二個關鍵的不一樣:

  1. 每一層能夠維護指定的文件個數,同時保證不讓key重疊。也就是說把key分區到不一樣的文件。所以在一層查找一個key,只用查找一個文件。第一層是特殊狀況,不知足上述條件,key能夠分佈在多個文件中。
  2. 每次,文件只會被合併到上一層的一個文件。當一層的文件數知足特定個數時,一個文件會被選出併合併到上一層。這明顯不一樣與另外一種合併方式:一些相近大小的文件被合併爲一個大文件。

這些改變代表按層合併的策略減少了合併操做的影響,同時減小了空間需求。除此以外,它也有更好的讀性能。可是對於大多數場景,整體的IO次數變的更多,一些更簡單的寫場景不適用。

總結

因此, LSM 是日誌和傳統的單文件索引(B+ tree,Hash Index)的中立,他提供一個機制來管理更小的獨立的索引文件(sstable)。

經過管理一組索引文件而不是單一的索引文件,LSM 將B+樹等結構昂貴的隨機IO變的更快,而代價就是讀操做要處理大量的索引文件(sstable)而不是一個,另外仍是一些IO被合併操做消耗。

經過以上的分析,應該知道LSM樹的由來了,LSM樹的設計思想很是樸素:將對數據的修改增量保持在內存中,達到指定的大小限制後將這些修改操做批量寫入磁盤,不過讀取的時候稍微麻煩,須要合併磁盤中歷史數據和內存中最近修改操做,因此寫入性能大大提高,讀取時可能須要先看是否命中內存,不然須要訪問較多的磁盤文件。極端的說,基於LSM樹實現的HBase的寫性能比Mysql高了一個數量級,讀性能低了一個數量級。

LSM樹原理把一棵大樹拆分紅N棵小樹,它首先寫入內存中,隨着小樹愈來愈大,內存中的小樹會flush到磁盤中,磁盤中的樹按期能夠作merge操做,合併成一棵大樹,以優化讀性能。

 

LSM的結構

LSM的基本思想是將修改的數據保存在內存,達到必定數量後在將修改的數據批量寫入磁盤,在寫入的過程當中與以前已經存在的數據作合併。同B樹存儲模型同樣,LSM存儲模型也支持增、刪、讀、改以及順序掃描操做。LSM模型利用批量寫入解決了隨機寫入的問題,雖然犧牲了部分讀的性能,可是大大提升了寫的性能。

MemTable

LSM自己由MemTable,Immutable MemTable,SSTable等多個部分組成,其中MemTable在內存,用於記錄最近修改的數據,通常用跳躍表來組織。當MemTable達到必定大小後,將其凍結起來變成Immutable MemTable,而後開闢一個新的MemTable用來記錄新的記錄。而Immutable MemTable則等待轉存到磁盤。

Immutable MemTable

所謂Immutable MemTable,便是隻能讀不能寫的內存表。內存部分已經有了MemTable,爲何還要使用Immutable MemTable?我的認爲其緣由是爲了避免阻塞寫操做。由於轉存的過程當中必然要保證內存表的記錄不變,不然若是新插入的記錄夾在兩條已經轉存到磁盤的記錄中間,處理上會很麻煩,轉存期間勢必要鎖住全表,這樣一來就會阻塞寫操做。因此不如將原有的MemTable變成只讀Immutable MemTable,在開闢一個新的MemTable用於寫入,即簡單,又不影響寫操做。

SSTable

SSTable是本意是指有序的鍵值對集合( a set of sorted key-value pairs )。是一個簡單有用的集合,正如它的名字同樣,它存儲的就是一系列的鍵值對。當文件較大的時候,還能夠爲其創建一個鍵-值的位置的索引,指明每一個鍵在SSTable文件中的偏移距離。這樣能夠加速在SSTable中的查詢。(固然這一點是可選的,同時讓我想去了Bitcask模型中hint文件,經過記錄 鍵-值的位置 ,來加速索引構建)


使用MemTable和SSTable這兩個組件,能夠構建一個最簡單的LSM存儲模型。這個模型與Bitcask模型相比,不存在啓動時間長的問題,可是這個模型的讀性能很是的差,由於一但在MemTable找不到相應的鍵,則須要在根據SSTable文件生成的時間,從最近到較早在SSTable中尋找,若是都不存在的話,則會遍歷完全部的SSTable文件。
若是SSTable文件個數不少或者沒有創建SSTable的文件內索引的話,讀性能則會大大降低。

除了在對SSTable內部創建索引外,還可使用Bloom Fileter,提升Key不在SSTable的斷定速度。一樣,按期合併舊的SSTable文件,在減小存儲的空間的同時,也能提升讀取的速度。下面這幅圖很好的描述了在LSM的大部分結構和操做


LevelDB如何優化讀性能

Leveldb是一個輕量級的,快速的以存儲爲目的的key-value存儲引擎。其使用的正是LSM存儲模型。咱們能夠看看LevelDB是如何來優化讀性能的。在LevelDB中,存在一種元信息文件MANIFEST,用於記錄leveldb的元信息,好比DB使用的Comparator名,以及各SSTable文件的管理信息:如Level層數、文件名、最小key和最大key等等。相比而言,元信息文件而SSTable文件的數目成正比,通常來講不會太多,是能夠載入內存的,所以Level能夠經過查詢元信息,從而判斷哪些文件中存在咱們須要的Key對應的記錄,減小SSTable文件讀取次數。此外,LevelDB的合併操做Compaction是分層次進行的,每一層都有多個SSTable文件,每次合併後除了Level0和內存的MemTable,Immutable MemTable中會有重複的鍵值外,LevelN(N>=1)的各層內部的SSTable文件不會再有重複的鍵值。同時,若是在Level N 層讀到了數據,那麼就不須要再日後讀Level N+1,Level N+2等層的數據了.由於Level N層的數據老是比Level N+1等層的數據更「新鮮」。

實現一個簡單的LSM存儲模型

根據上面講述的原理,實現了一個簡單的LSM模型(https://github.com/ym65536/Distributed_System/blob/master/Storage/LSM_Tree.py)。這個模型也內存表爲一個跳躍表,SSTable就是簡單的有序鍵值對集合,沒有SSTable內部使用索引,沒有使用Bloom過濾器。其實能就是將我以前的Bitcask模型進行了簡單的改造:

  • 將原來的哈希表換成了跳躍表;
  • 原來讀取記錄徹底依賴哈希表,如今若是在跳躍表中沒有的話,就去讀取文件SSTable文件中的數據,根據文件編號從大到小進行,編號越大,表示數據越新;
  • 去掉了加載數據的功能(LSM不須要);

簡單起見,沒有完成對範圍掃描的支持,不過內存表和SSTable都是有序的,所以這個也不是很難。

相關文章
相關標籤/搜索