# LevelDB簡單介紹
------
LevelDB是Google開源的持久化KV單機數據庫,具有很高的隨機寫,順序讀/寫性能,但是隨機讀的性能很一般,也就是說,LevelDB很適合應用在查詢較少,而寫很多的場景。LevelDB應用了`LSM (Log Structured Merge)` 策略,`lsm_tree`對索引變更進行延遲及批量處理,并通過一種類似于歸并排序的方式高效地將更新遷移到磁盤,降低索引插入開銷,關于LSM,本文在后面也會簡單提及。
根據Leveldb官方網站的描述,LevelDB的特點和限制如下:
特點:
1、key和value都是任意長度的字節數組;
2、entry(即一條K-V記錄)默認是按照key的字典順序存儲的,當然開發者也可以重載這個排序函數;
3、提供的基本操作接口:Put()、Delete()、Get()、Batch();
4、支持批量操作以原子操作進行;
5、可以創建數據全景的snapshot(快照),并允許在快照中查找數據;
6、可以通過前向(或后向)迭代器遍歷數據(迭代器會隱含的創建一個snapshot);
7、自動使用Snappy壓縮數據;
8、可移植性;
限制:
1、非關系型數據模型(NoSQL),不支持sql語句,也不支持索引;
2、一次只允許一個進程訪問一個特定的數據庫;
3、沒有內置的C/S架構,但開發者可以使用LevelDB庫自己封裝一個server;
下面展示了一個db文件夾下的文件內容
1. .log
2. CURRENT
3. LOCK
4. LOG
5. MAINFEST
這幾個文件是leveldb在磁盤上的最主要的表現形式,還有一些比較重要的文件`.sst文件`沒有展示出(這是因為當前db沒有數據寫入,或是數據量太小還在LOG文件中)
leveldb在內存中還存在兩種數據存儲形式`MemTable`和`Immutable MemTable`,以上的文件和數據結構組成了leveldb基本存儲框架
? 下圖是LevelDB運行一段時間后的存儲模型快照:
下面我將一一講解這些文件的作用
###**MAINFEST文件**
SSTable中的某個文件屬于特定層級,而且其存儲的記錄是key有序的,那么必然有文件中的最小key和最大key,這是非常重要的信息,Manifest 就記載了SSTable各個文件的管理信息,比如屬于哪個Level,文件名稱叫啥,最小key和最大key各自是多少。下圖是Manifest所存儲內容的示意:
###**CURRENT文件**
隨之SSTable的不斷合并,新文件不斷產生,老文件合并后被丟棄Manifest也會跟著反映這種變化,此時往往會新生成Manifest文件來記載這種變化,而Current則用來指出哪個Manifest文件才是我們關心的那個Manifest文件。
.log文件和SSTable文件以及MemTable都是K-V([]byte-[]byte)形式存儲的。
###**LOCK文件**
LOCK文件主要是用于獲取文件鎖,或者給文件上鎖用。(在fabric中peer節點就是以互斥鎖的形式打開文件,使得外部打開文件只能以read-only+不獲取文件鎖的形式去做)
###**sst文件**
sst文件并不是分散在leveldb中存儲,而是以層級結構存儲的,每個level存在許多sst文件,每個sst文件大小上限為2MB,而一個sst文件又是由若干個block組成,每個block的大小為4K,這些block是讀寫的最小單元。SST文件的最后一個block是一個index,指向每個datablock的起始位置,以及每個block第一個entry的key值(block內的key有序存儲)
由log直接讀取的entry會寫到Level 0的SST中(最多4個文件);
當Level 0的4個文件都存儲滿了,會選擇其中一個文件Compact到Level 1的SST中
注意:
>* Level 0的SSTable文件和其它Level的文件相比有特殊性:這個層級內的.sst文件,兩個文件可能存在key重疊,比如有兩個level 0的sst文件,文件A和文件B,文件A的key范圍是:{bar, car},文件B的Key范圍是{blue,samecity},那么很可能兩個文件都存在key=”blood”的記錄。對于其它Level的SSTable文件來說,則不會出現同一層級內.sst文件的key重疊現象,就是說LevelL中任意兩個.sst文件,那么可以保證它們的key值是不會重疊的。
Log:最大4MB (可配置), 會寫入Level 0;
Level 0:最多4個SST文件,;
Level 1:總大小不超過10MB;
Level 2:總大小不超過100MB;
Level 3+:總大小不超過上一個Level ×10的大小。
比如:0 ? 4 SST, 1 ? 10M, 2 ? 100M, 3 ? 1G, 4 ? 10G, 5 ? 100G, 6 ? 1T, 7 ? 10T
在讀操作中,要查找一條entry,先查找log,如果沒有找到,然后在Level 0中查找,如果還是沒有找到,再依次往更底層的Level順序查找;如果查找了一條不存在的entry,則要遍歷一遍所有的Level才能返回"Not Found"的結果。
在寫操作中,新數據總是先插入開頭的幾個Level中,開頭的這幾個Level存儲量也比較小,因此,對某條entry的修改或刪除操作帶來的性能影響就比較可控。
可見,SST采取分層結構是為了最大限度減小插入新entry時的開銷
##**寫操作**
1、順序寫入磁盤log文件;
2、由log文件寫入內存memtable(采用skiplist結構實現);
3、寫入磁盤SST文件(sorted string table files),這步是數據歸檔的過程(永久化存儲);
注意:
>* log文件的作用是是用于系統崩潰恢復而不丟失數據,假如沒有Log文件,因為寫入的記錄剛開始是保存在內存中的,此時如果系統崩潰,內存中的數據還沒有來得及Dump到磁盤,所以會丟失數據;
>* 在寫memtable時,如果其達到checkpoint(滿員)的話,會將其改成immutable memtable(只讀),然后等待dump到磁盤SST文件中,此時也會生成新的memtable供寫入新數據;
>* memtable和sst文件中的key都是有序的,log文件的key是無序的;
>* LevelDB刪除操作也是插入,只是標記Key為刪除狀態,真正的刪除要到Compaction的時候才去做真正的操作;
>* LevelDB沒有更新接口,如果需要更新某個Key的值,只需要插入一條新紀錄即可;或者先刪除舊記錄,再插入也可以;
##**讀操作**
1、在內存中依次查找memtable、immutable memtable;
2、如果配置了cache,查找cache;
3、根據mainfest索引文件,在磁盤中查找SST文件;
舉個例子:我們先往levelDb里面插入一條數據 {key="www.samecity.com"? value="我們"},過了幾天,samecity網站改名為:69同城,此時我們插入數據{key="www.samecity.com"value="69同城"},同樣的key,不同的value;邏輯上理解好像levelDb中只有一個存儲記錄,即第二個記錄,但是在levelDb中很可能存在兩條記錄,即上面的兩個記錄都在levelDb中存儲了,此時如果用戶查詢 key="www.samecity.com",我們當然希望找到最新的更新記錄,也就是第二個記錄返回,因此,查找的順序應該依照數據更新的新鮮度來,對于SSTable文件來說,如果同時在level L和Level L+1找到同一個key,level L的信息一定比level L+1的要新。
##**Compaction操作**
對于LevelDb來說,寫入記錄操作很簡單,刪除記錄僅僅寫入一個刪除標記就算完事,但是讀取記錄比較復雜,需要在內存以及各個層級文件中依照新鮮程度依次查找,代價很高。為了加快讀取速度,levelDb采取了compaction的方式來對已有的記錄進行整理壓縮,通過這種方式,來刪除掉一些不再有效的KV數據,減小數據規模,減少文件數量等。
LevelDb的compaction機制和過程與Bigtable所講述的是基本一致的,Bigtable(Google設計的分布式數據存儲系統)中講到三種類型的compaction: minor ,major和full():
>* minor Compaction,就是把memtable中的數據導出到SSTable文件中;
>* major compaction就是合并不同層級的SSTable文件;
>* full compaction就是將所有SSTable進行合并;
LevelDb包含其中兩種,minor和major。
Minor compaction 的目的是當內存中的memtable大小到了一定值時,將內容保存到磁盤文件中,如下圖:
immutable memtable其實是一個SkipList,其中的記錄是根據key有序排列的,遍歷key并依次寫入一個level 0 的新建SSTable文件中,寫完后建立文件的index數據,這樣就完成了一次minor compaction。從圖中也可以看出,對于被刪除的記錄,在minor? compaction 過程中并不真正刪除這個記錄,原因也很簡單,這里只知道要刪掉key記錄,但是這個KV數據在哪里?那需要復雜的查找,所以在minor compaction的時候并不做刪除,只是將這個key作為一個記錄寫入文件中,至于真正的刪除操作,在以后更高層級的compaction中會去做。
當某個level下的SSTable文件數目超過一定設置值后,levelDb會從這個level的SSTable中選擇一個文件(level>0),將其和高一層級的level+1的SSTable文件合并,這就是major compaction。
### LSM Tree(Log-Structured Merge Tree)
存儲引擎和B樹存儲引擎一樣,同樣支持增、刪、讀、改、順序掃描操作。而且通過批量存儲技術規避磁盤隨機寫入問題。當然凡事有利有弊,LSM樹和B+樹相比,LSM樹犧牲了部分讀性能,用來大幅提高寫性能。
LSM樹的設計思想非常樸素:將對數據的修改增量保持在內存中,達到指定的大小限制后將這些修改操作批量寫入磁盤,不過讀取的時候稍微麻煩,需要合并磁盤中歷史數據和內存中最近修改操作,所以寫入性能大大提升,讀取時可能需要先看是否命中內存,否則需要訪問較多的磁盤文件。
LSM樹原理把一棵大樹拆分成N棵小樹,它首先寫入內存中,隨著小樹越來越大,內存中的小樹會flush到磁盤中,磁盤中的樹定期可以做merge操作,合并成一棵大樹,以優化讀性能。
LSM Tree,對于最簡單的二層LSMTree而言,內存中的數據和磁盤中的數據merge操作如下: