LSM Tree

關於LSM Tree的介紹,這篇文章http://www.benstopford.com/2015/02/14/log-structured-merge-trees/講得很是具體。html

背景

因爲磁盤的順序IO比隨機IO效率高得多,爲了提升寫的吞吐量,有如下幾個方法:termed logging,journalling, a heap filesql

這種狀況下,讀操做會比寫操做花費更多時間(須要反向掃描,直至找到key)。數據庫

基於log/journal的方法只適用於簡單場景,如數據被總體訪問(大部分數據庫的預寫日誌,WAL, write-ahead logging),或經過已知偏移量訪問,如簡單的消息系統kafka。緩存

對於複雜場景,如基於key或隨機訪問,有四種方式:數據結構

1. Searched sorted file:將數據存入文件,用key排序,若數據定義了長度,則使用binary search,不然使用page index + scanoop

2. Hash:將數據hash到桶,以後直接讀取性能

3. B+:使用可導航的文件組織,如B+樹,ISAM等優化

4. 外部文件:將數據存爲log/heap,使用額外的hash或tree索引至數據設計

以上四種方式大大提高了讀性能(大部分狀況下O(lgn)),但也犧牲了寫性能(因爲增長了排序)。日誌

此時,有如下問題:

1. 每次寫操做有兩次IO,一次是讀頁面,一次是寫回該頁面,而log法只須要一次IO

2. 若要更新hash或B+索引結構,則需更新文件系統的特定部分,而這種原地更新須要緩慢的隨機IO

一個常見的解決方法是使用方法4,爲journal構建index,並將index至於內存。因而Log Structured Merge Trees(LSM Tree)應運而生。

LSM Tree

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

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

具體工做原理爲:當一個更新請求到達時,將會被加入內存緩存區(通常狀況下是一棵樹,如紅黑樹,B樹等,來保存key的有序性)。這個memtable會做爲write-ahead-log複製於磁盤中,以便出現問題時的修復。當memtable區滿時,將會flush到磁盤中做爲一個新的文件。這個過程會隨着寫入的增多而不斷重複。

因爲舊文件不會被更新,重複的entry會被建立來取代以前的記錄(或者移除的標記),這會帶來一些冗餘。系統會按期進行壓縮操做,壓縮的作法是選擇多個文件,將他們進行合併,並移除重複的更新或者刪除操做。這對消除冗餘和提升讀性能(因爲讀性能會隨着文件數增長而遞減)很是重要。此外,因爲每一個文件都是有序的,因此合併文件的操做也很是高效。

當讀操做到達時,系統會先檢查memtable,若是沒有找到對應的key,則在磁盤文件中以逆時間順序進行查找,直至找到key。每一個文件都是有序的,不過讀操做仍然會由於文件數目的增加而變慢。爲了解決這個問題,有一些小trick。其中最多見的方法是在內存中保持一個page-index,以便使你更接近目標key。LevelDB,RocksDB和BigTable在每一個文件的保存了一個block-index。這會比直接進行二分查找更高效,由於它容許使用可變長度,更適用於壓縮數據。

哪怕使用緊湊的讀操做,文件訪問次數依然不少。大部分實現經過使用Bloomfilter來進行改進。Bloom filters是一種判斷文件中是否包含某個key的內存高效方法。

 

總的來講,LSM Tree是在隨機寫IO和隨機讀IO之間進行trade off。若是可使用軟件方法(如Bloom filters)或硬件方法(如大文件緩存)來優化讀性能,那麼這個trade off是一個明智的選擇。

基本compaction

根據特定size限制進行compaction,譬如5個文件,每一個文件有10行,會被合併爲一個有50行(或者比50行小一些)的文件。而5個50行的文件又會被合併爲一個具備250行的文件,以此類推。

這種方法的問題是:會建立大量的文件,全部文件都須要被分別搜索以讀取結果。

分層compaction

新的實現方法,如LevelDB,RocksDB和Cassandra等,經過level-based而不是size-based進行compaction以解決上述問題。這種level-based方法主要有如下兩點不一樣:

1. 每一層能夠包含一系列文件,而且保證不會有重疊的key。這意味着key在全部可用文件中被切分。因此在特定層尋找一個key只須要讀取一個文件。(要注意的是,第一層比較特殊,相同keys能夠在多個文件中)

2. 多個文件會一次合併至上層的一個文件中。當一層填滿時,會從該層取出一個文件,合併至上層以建立空間使更多的數據能夠被加入。

這種改動意味着level-based解決方案隨着時間推移進行壓縮而且須要的空間更少。此外,它的讀性能也更好。

HBase

HBase存儲主要原理爲:

1. 小樹先寫到內存中,爲了防止內存數據丟失,寫內存的同時須要暫時持久化到磁盤,對應了HBase的MemStore和HLog

2. MemStore上的樹達到必定大小以後,須要flush到HRegion磁盤中(通常是Hadoop DataNode),這樣MemStore就變成了DataNode上的磁盤文件StoreFile,HRegionServer按期對DataNode的數據作merge操做,完全刪除無效空間,多棵小樹在這個時機合併成大樹,來加強讀性能。

LevelDB

LevelDB一樣也利用了LSM Tree,這篇文章http://www.cnblogs.com/haippy/archive/2011/12/04/2276064.html講得很是具體。這裏只簡單介紹一下其整體結構以下:

LevelDb的Log文件和Memtable與Bigtable論文中介紹的是一致的,當應用寫入一條Key:Value記錄的時候,LevelDb會先往log文件裏寫入,成功後將記錄插進Memtable中,這樣基本就算完成了寫入操做,由於一次寫入操做只涉及一次磁盤順序寫和一次內存寫入,因此這是爲什麼說LevelDb寫入速度極快的主要緣由。LevelDb的Memtable採用了SkipList數據結構(Redis也使用了該數據結構提升插入效率)。

SSTable中的文件是Key有序的,就是說在文件中小key記錄排在大Key記錄以前,各個Level的SSTable都是如此,可是這裏須要注意的一點是:Level 0的SSTable文件(後綴爲.sst)和其它Level的文件相比有特殊性:這個層級內的.sst文件,兩個文件可能存在key重疊。對於其它Level的SSTable文件來講,則不會出現同一層級內.sst文件的key重疊現象。

SSTable中的某個文件屬於特定層級,並且其存儲的記錄是key有序的,那麼必然有文件中的最小key和最大key,這是很是重要的信息,LevelDb應該記下這些信息。Manifest就是幹這個的,它記載了SSTable各個文件的管理信息,好比屬於哪一個Level,文件名稱叫啥,最小key和最大key各自是多少。下圖是Manifest所存儲內容的示意:

Current文件的內容只有一個信息,就是記載當前的manifest文件名。由於在LevleDb的運行過程當中,隨着Compaction的進行,SSTable文件會發生變化,會有新的文件產生,老的文件被廢棄,Manifest也會跟着反映這種變化,此時每每會新生成Manifest文件來記載這種變化,而Current則用來指出哪一個Manifest文件纔是咱們關心的那個Manifest文件。

相關文章
相關標籤/搜索