前言
LSM樹全稱為The Log structured Merage - Tree,根據(jù)名稱可以大概認(rèn)識(shí)到主要有以下特點(diǎn):
- 是基于日志結(jié)構(gòu)思想的。其中日志結(jié)構(gòu)主要特點(diǎn)就是可以不斷追加,而不做原始日志的覆蓋。
- 要進(jìn)行Merage,即要把日志結(jié)構(gòu)進(jìn)行Merage。
所以根據(jù)名稱可以大概推測LSM的一些基本特性。
概念
LSM的思想起源于1996年的一篇論文(點(diǎn)擊查看)
LSM的核心思想:將數(shù)據(jù)的添加和增量修改首先保存在內(nèi)存中(追加日志的形式),而后滿足一定條件后,將內(nèi)存中的數(shù)據(jù)flush到磁盤上。磁盤中按照不同層級(jí)組織數(shù)據(jù)文件,內(nèi)存中的數(shù)據(jù)首先flush到某一層級(jí),而后當(dāng)該層級(jí)滿足一定條件后,會(huì)和磁盤上下一個(gè)層級(jí)的數(shù)據(jù)文件進(jìn)行合并(Merage)。為了保證查詢性能,內(nèi)存中數(shù)據(jù)和磁盤中不同層級(jí)的數(shù)據(jù)都需要保持有序。如下圖所示:
關(guān)鍵流程
寫入
LSM的寫入流程如下:1)首先寫WAL日志,避免故障導(dǎo)致數(shù)據(jù)丟失;2)將數(shù)據(jù)寫入C0內(nèi)存中memtable,相當(dāng)于在內(nèi)存中保留最新的數(shù)據(jù)更新。3)當(dāng)C0內(nèi)存中的數(shù)據(jù)達(dá)到一定量級(jí)的時(shí)候(大小限制)會(huì)將C0中的數(shù)據(jù)flush到磁盤C1中,并且對(duì)磁盤C1層級(jí)的數(shù)據(jù)進(jìn)行合并(歸并排序)。4)當(dāng)磁盤C1層級(jí)大小到一定規(guī)模,又會(huì)將C1中的數(shù)據(jù)同C2合并(歸并排序)。5)以此類推,上一層級(jí)的數(shù)據(jù)文件達(dá)到一定規(guī)模后,會(huì)向下一層級(jí)合并。
特別說明
- 由于后面的磁盤文件合并是后臺(tái)異步進(jìn)行,不會(huì)阻塞數(shù)據(jù)寫入內(nèi)存,所以LSM的數(shù)據(jù)寫入性能很高。
- 磁盤上不同層級(jí)數(shù)據(jù)文件合并時(shí),會(huì)將原有本層級(jí)的數(shù)據(jù)刪掉,重新寫入新的數(shù)據(jù)文件,使磁盤是順序?qū)懀@也避免了磁盤的時(shí)間消耗。
- 磁盤不同層級(jí)數(shù)據(jù)文件合并時(shí),都會(huì)對(duì)本層級(jí)數(shù)據(jù)進(jìn)行歸并排序,也就是說,每一層級(jí)的數(shù)據(jù)永遠(yuǎn)都是有序的,這是為了保證查詢時(shí)的效率。
查詢
LSM的查詢比較麻煩,由于內(nèi)存中只保留了最近更新的數(shù)據(jù),而且磁盤上不同層級(jí)的數(shù)據(jù)也不是完全完整的數(shù)據(jù),越靠上層的數(shù)據(jù)越新,越靠下層的數(shù)據(jù)越老,所以查詢時(shí)需要從上至下分別查詢多次。1)首先查看內(nèi)存中是否包含指定目標(biāo)。若查到則直接返回,若沒有查到則繼續(xù)向下查找。2)其次查看磁盤C1層級(jí)中是否包含指定目標(biāo)。以此類推,一直向下層級(jí)查找,直到命中指定目標(biāo)或者查完所有層級(jí)后仍然沒有命中(不存在)。
特別說明
- 由于上述查詢的特性,所以LSM更適合高密度寫入,低密度查詢的場景。
- 有很多對(duì)于LSM查詢的優(yōu)化,比如增加布隆過濾器,來直接判斷該key是否存在,而避免遍歷所有層級(jí)的磁盤文件。又比如會(huì)增加一個(gè)類似于元數(shù)據(jù)的結(jié)構(gòu)(levelDB中的Mainfest 文件,它描述各個(gè)層級(jí)數(shù)據(jù)的key最小值,最大值,層級(jí)數(shù))查詢時(shí)可以直接通過該結(jié)構(gòu)快速定位要查找的數(shù)據(jù)層級(jí)。
更新
k-v的更新也就是寫入的流程,內(nèi)存中都是向后追加最新的數(shù)據(jù)記錄,磁盤中合并的時(shí)候較新的數(shù)據(jù)值(上層級(jí))會(huì)覆蓋較老的數(shù)據(jù)值(下層級(jí)),每個(gè)層級(jí)合并完之后僅保留唯一并且有序的key。
刪除
由于LSM不會(huì)直接在內(nèi)存或磁盤上直接刪除某個(gè)key,而是發(fā)生刪除請求的時(shí)候追加一個(gè)特定標(biāo)志的數(shù)據(jù)記錄,比如 key-del,這意味該key被做了刪除標(biāo)記。只有當(dāng)磁盤中數(shù)據(jù)文件合并的時(shí)候才會(huì)執(zhí)行真正的刪除。
LSM的優(yōu)劣性
- 將用戶隨機(jī)寫,轉(zhuǎn)換為順序?qū)懀宰芳拥姆绞娇焖賹懭耄@些要素保證了其寫入性能高,吞吐量大。
- 由于需要多次分批查詢,導(dǎo)致讀性能相對(duì)較差。(這個(gè)讀性能查是相對(duì)的,在實(shí)際場景中,并不一定性能差)
- 讀寫放大問題:讀寫放大(read and write amplification)是 LSM-tree 的主要問題。讀寫放大 = 磁盤上實(shí)際讀寫的數(shù)據(jù)量 / 用戶需要的數(shù)據(jù)量。由于磁盤IO帶寬是共享的,特殊情況下,會(huì)對(duì)用戶讀寫造成一定影響。
LSM的實(shí)現(xiàn)
LSM思想提出后,有很多基于該結(jié)構(gòu)的數(shù)據(jù)庫或文件系統(tǒng)的出現(xiàn)。并且在此基礎(chǔ)上實(shí)現(xiàn)時(shí)對(duì)一些細(xì)節(jié)都做了不同的優(yōu)化。
LevelDB、RocksDB、Cassandra、Hbase以及一些時(shí)序數(shù)據(jù)庫(InfluxDB)都實(shí)現(xiàn)或借鑒了LSM的思想。
以下分別就LevelDB 和 Hbase 中的關(guān)鍵結(jié)構(gòu)說明一下。
LevelDB
上圖為LevelDB的基本架構(gòu)。其中內(nèi)存中有兩種類型的memtable:一種是正常的memtable,接受用戶的寫入數(shù)據(jù)更新。一種是Immutable,將作為固定的不可更新的memtable。硬盤中包括若干個(gè)SStable,他們分別按照L0到L6進(jìn)行分層,每一層都包含多個(gè)SStable文件,每一層的容量大小限制均為上一層的10倍。
寫入時(shí):1)還是先寫入wal日志。2)然后寫入memtable。3)當(dāng)memtable滿足容量限制后,則轉(zhuǎn)換為immutable,停止接受數(shù)據(jù)寫入,并再新開啟一個(gè)memtable接收新數(shù)據(jù)的寫入。這樣的處理使得數(shù)據(jù)寫入更加的連續(xù),不會(huì)受到flush磁盤的影響。4)immutable的數(shù)據(jù)會(huì)直接flush到L0層,并且采取向L0層直接追加SStable文件而不是合并的方式添加,所以L0層的SStables中可能存在冗余數(shù)據(jù)(重復(fù)key)。5)然后L0超過限制后,會(huì)選擇一個(gè)(最老)SStable同下層級(jí)進(jìn)行合并,然后下一層級(jí)所有受此影響的SStable都會(huì)進(jìn)行更新。更新完成后保證該層級(jí)(所有SStable)中的數(shù)據(jù)是唯一有序的。
查詢時(shí):依次memtable、immutable、L0、L1-L6
Hbase
數(shù)據(jù)先寫入到內(nèi)存中,對(duì)應(yīng)HRegion中的memStore。
為了防止數(shù)據(jù)丟失同時(shí)寫Log(類似WAL),對(duì)應(yīng)HRegion中的Hlog文件。
MemStore 到達(dá)一定大小后,刷磁盤,對(duì)應(yīng)HRegion中的StoreFile(其中Hfile是真正的數(shù)據(jù)文件,并且里面還會(huì)包含類似于布隆過濾器的結(jié)構(gòu)以及索引數(shù)據(jù))。
HRegion內(nèi)部的StoreFile 會(huì)不斷合并更新。
總結(jié)
傳統(tǒng)關(guān)系型數(shù)據(jù)庫的索引一般是通過B/B+樹結(jié)構(gòu)實(shí)現(xiàn)的,B+樹為了保證查詢性能,其索引結(jié)構(gòu)必須保證全局有序,所以這就導(dǎo)致在添加數(shù)據(jù)的時(shí)候需要樹節(jié)點(diǎn)而引起重新排序(樹平衡),也就造成了大量隨機(jī)寫磁盤的操作,影響效率。
LSM的重要思想就是使大量隨機(jī)寫變成了順序?qū)懀蟠筇岣吡藬?shù)據(jù)寫入的性能,但是犧牲了部分讀性能。