LevelDB
一、LevelDB入門
LevelDB是Google開源的持久化KV單機(jī)數(shù)據(jù)庫(kù),具有很高的隨機(jī)寫,順序讀/寫性能,但是隨機(jī)讀的性能很一般,也就是說,LevelDB很適合應(yīng)用在查詢較少,而寫很多的場(chǎng)景。LevelDB應(yīng)用了LSM?(Log Structured Merge) 策略,lsm_tree對(duì)索引變更進(jìn)行延遲及批量處理,并通過一種類似于歸并排序的方式高效地將更新遷移到磁盤,降低索引插入開銷,關(guān)于LSM,本文在后面也會(huì)簡(jiǎn)單提及。
?
根據(jù)Leveldb官方網(wǎng)站的描述,LevelDB的特點(diǎn)和限制如下:
特點(diǎn):
1、key和value都是任意長(zhǎng)度的字節(jié)數(shù)組;
2、entry(即一條K-V記錄)默認(rèn)是按照key的字典順序存儲(chǔ)的,當(dāng)然開發(fā)者也可以重載這個(gè)排序函數(shù);
3、提供的基本操作接口:Put()、Delete()、Get()、Batch();
4、支持批量操作以原子操作進(jìn)行;
5、可以創(chuàng)建數(shù)據(jù)全景的snapshot(快照),并允許在快照中查找數(shù)據(jù);
6、可以通過前向(或后向)迭代器遍歷數(shù)據(jù)(迭代器會(huì)隱含的創(chuàng)建一個(gè)snapshot);
7、自動(dòng)使用Snappy壓縮數(shù)據(jù);
8、可移植性;
限制:
1、非關(guān)系型數(shù)據(jù)模型(NoSQL),不支持sql語句,也不支持索引;
2、一次只允許一個(gè)進(jìn)程訪問一個(gè)特定的數(shù)據(jù)庫(kù);
3、沒有內(nèi)置的C/S架構(gòu),但開發(fā)者可以使用LevelDB庫(kù)自己封裝一個(gè)server;
?
?python 版示例
安裝依賴
pip install ?leveldb
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | python 2.7 示例<br>>>> import leveldb >>> db = leveldb.LevelDB('./db') >>> db.Put('hello',?'world') >>> print db.Get('hello') world >>> db.Delete('hello') >>> db.Get('hello') Traceback (most recent call last): ??File?"<stdin>", line 1,?in?<module> KeyError >>>?for?i?in?xrange(10): ...?? db.Put(str(i),?'string_%s'?% i) ... >>> print list(db.RangeIter(key_from =?'2', key_to =?'5')) [('2',?'string_2'), ('3',?'string_3'), ('4',?'string_4'), ('5',?'string_5')] >>> batch = leveldb.WriteBatch() >>>?for?i?in?xrange(1000): ...?? db.Put(str(i),?'string_%s'?% i) ... >>> db.Write(batch, sync = True)<br><br>python 3.5 將 字符串進(jìn)行編碼 如?'key'.encode() ,''.decode() <br><br>用于python3 sock通信<br> |
插入數(shù)據(jù)示例
?
| 1 2 3 4 5 6 7 | import leveldb db = leveldb.LevelDB('./db') batch = leveldb.WriteBatch() for?i?in?xrange(1000000): ...???? db.Put(str(i),'string_%s'?% i) ... db.Write(batch,sync=True) |
?
下面生成一個(gè)目錄db,里面包含若干文件:
?
?
然后簡(jiǎn)要說下各個(gè)文件的含義:
1、CURRENT
2、LOG
3、LOCK
4、MANIFEST
?
下圖是LevelDB運(yùn)行一段時(shí)間后的存儲(chǔ)模型快照:內(nèi)存中的MemTable和Immutable MemTable以及磁盤上的幾種主要文件:Current文件,Manifest文件,log文件以及SSTable文件。當(dāng)然,LevelDb除了這六個(gè)主要部分還有一些輔助的文件,但是以上六個(gè)文件和數(shù)據(jù)結(jié)構(gòu)是LevelDb的主體構(gòu)成元素。
?
log文件、MemTable、SSTable文件都是用來存儲(chǔ)k-v記錄的,下面再說說manifest和Current文件的作用。
SSTable中的某個(gè)文件屬于特定層級(jí),而且其存儲(chǔ)的記錄是key有序的,那么必然有文件中的最小key和最大key,這是非常重要的信息,Manifest 就記載了SSTable各個(gè)文件的管理信息,比如屬于哪個(gè)Level,文件名稱叫啥,最小key和最大key各自是多少。下圖是Manifest所存儲(chǔ)內(nèi)容的示意:
?
另外,在LevleDb的運(yùn)行過程中,隨著Compaction的進(jìn)行,SSTable文件會(huì)發(fā)生變化,會(huì)有新的文件產(chǎn)生,老的文件被廢棄,Manifest也會(huì)跟著反映這種變化,此時(shí)往往會(huì)新生成Manifest文件來記載這種變化,而Current則用來指出哪個(gè)Manifest文件才是我們關(guān)心的那個(gè)Manifest文件。
?
?
二、讀寫數(shù)據(jù)
?
?
寫操作流程:
1、順序?qū)懭氪疟Plog文件;
2、寫入內(nèi)存memtable(采用skiplist結(jié)構(gòu)實(shí)現(xiàn));
3、寫入磁盤SST文件(sorted string table files),這步是數(shù)據(jù)歸檔的過程(永久化存儲(chǔ));
?
注意:
- log文件的作用是是用于系統(tǒng)崩潰恢復(fù)而不丟失數(shù)據(jù),假如沒有Log文件,因?yàn)閷懭氲挠涗泟傞_始是保存在內(nèi)存中的,此時(shí)如果系統(tǒng)崩潰,內(nèi)存中的數(shù)據(jù)還沒有來得及Dump到磁盤,所以會(huì)丟失數(shù)據(jù);
- 在寫memtable時(shí),如果其達(dá)到check point(滿員)的話,會(huì)將其改成immutable memtable(只讀),然后等待dump到磁盤SST文件中,此時(shí)也會(huì)生成新的memtable供寫入新數(shù)據(jù);
- memtable和sst文件中的key都是有序的,log文件的key是無序的;
- LevelDB刪除操作也是插入,只是標(biāo)記Key為刪除狀態(tài),真正的刪除要到Compaction的時(shí)候才去做真正的操作;
- LevelDB沒有更新接口,如果需要更新某個(gè)Key的值,只需要插入一條新紀(jì)錄即可;或者先刪除舊記錄,再插入也可;
?
?
讀操作流程:
1、在內(nèi)存中依次查找memtable、immutable memtable;
2、如果配置了cache,查找cache;
3、根據(jù)mainfest索引文件,在磁盤中查找SST文件;
?
?
舉個(gè)例子:我們先往levelDb里面插入一條數(shù)據(jù) {key="www.samecity.com" ?value="我們"},過了幾天,samecity網(wǎng)站改名為:69同城,此時(shí)我們插入數(shù)據(jù){key="www.samecity.com" ?value="69同城"},同樣的key,不同的value;邏輯上理解好像levelDb中只有一個(gè)存儲(chǔ)記錄,即第二個(gè)記錄,但是在levelDb中很可能存在兩條記錄,即上面的兩個(gè)記錄都在levelDb中存儲(chǔ)了,此時(shí)如果用戶查詢key="www.samecity.com",我們當(dāng)然希望找到最新的更新記錄,也就是第二個(gè)記錄返回,因此,查找的順序應(yīng)該依照數(shù)據(jù)更新的新鮮度來,對(duì)于SSTable文件來說,如果同時(shí)在level L和Level L+1找到同一個(gè)key,level L的信息一定比level L+1的要新。
?
?
?
?
三、SSTable文件
SST文件并不是平坦的結(jié)構(gòu),而是分層組織的,這也是LevelDB名稱的來源。
SST文件的一些實(shí)現(xiàn)細(xì)節(jié):
1、每個(gè)SST文件大小上限為2MB,所以,LevelDB通常存儲(chǔ)了大量的SST文件;
2、SST文件由若干個(gè)4K大小的blocks組成,block也是讀/寫操作的最小單元;
3、SST文件的最后一個(gè)block是一個(gè)index,指向每個(gè)data block的起始位置,以及每個(gè)block第一個(gè)entry的key值(block內(nèi)的key有序存儲(chǔ));
4、使用Bloom filter加速查找,只要掃描index,就可以快速找出所有可能包含指定entry的block。
5、同一個(gè)block內(nèi)的key可以共享前綴(只存儲(chǔ)一次),這樣每個(gè)key只要存儲(chǔ)自己唯一的后綴就行了。如果block中只有部分key需要共享前綴,在這部分key與其它key之間插入"reset"標(biāo)識(shí)。
?
由log直接讀取的entry會(huì)寫到Level 0的SST中(最多4個(gè)文件);
當(dāng)Level 0的4個(gè)文件都存儲(chǔ)滿了,會(huì)選擇其中一個(gè)文件Compact到Level 1的SST中;
注意:Level 0的SSTable文件和其它Level的文件相比有特殊性:這個(gè)層級(jí)內(nèi)的.sst文件,兩個(gè)文件可能存在key重疊,比如有兩個(gè)level 0的sst文件,文件A和文件B,文件A的key范圍是:{bar, car},文件B的Key范圍是{blue,samecity},那么很可能兩個(gè)文件都存在key=”blood”的記錄。對(duì)于其它Level的SSTable文件來說,則不會(huì)出現(xiàn)同一層級(jí)內(nèi).sst文件的key重疊現(xiàn)象,就是說Level L中任意兩個(gè).sst文件,那么可以保證它們的key值是不會(huì)重疊的。
?
Log:最大4MB (可配置), 會(huì)寫入Level 0;
Level 0:最多4個(gè)SST文件,;
Level 1:總大小不超過10MB;
Level 2:總大小不超過100MB;
Level 3+:總大小不超過上一個(gè)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"的結(jié)果。
在寫操作中,新數(shù)據(jù)總是先插入開頭的幾個(gè)Level中,開頭的這幾個(gè)Level存儲(chǔ)量也比較小,因此,對(duì)某條entry的修改或刪除操作帶來的性能影響就比較可控。
可見,SST采取分層結(jié)構(gòu)是為了最大限度減小插入新entry時(shí)的開銷;
?
?
Compaction操作
對(duì)于LevelDb來說,寫入記錄操作很簡(jiǎn)單,刪除記錄僅僅寫入一個(gè)刪除標(biāo)記就算完事,但是讀取記錄比較復(fù)雜,需要在內(nèi)存以及各個(gè)層級(jí)文件中依照新鮮程度依次查找,代價(jià)很高。為了加快讀取速度,levelDb采取了compaction的方式來對(duì)已有的記錄進(jìn)行整理壓縮,通過這種方式,來刪除掉一些不再有效的KV數(shù)據(jù),減小數(shù)據(jù)規(guī)模,減少文件數(shù)量等。
LevelDb的compaction機(jī)制和過程與Bigtable所講述的是基本一致的,Bigtable中講到三種類型的compaction: minor ,major和full:
- minor Compaction,就是把memtable中的數(shù)據(jù)導(dǎo)出到SSTable文件中;
- major compaction就是合并不同層級(jí)的SSTable文件;
- full compaction就是將所有SSTable進(jìn)行合并;
LevelDb包含其中兩種,minor和major。
Minor compaction 的目的是當(dāng)內(nèi)存中的memtable大小到了一定值時(shí),將內(nèi)容保存到磁盤文件中,如下圖:
?
immutable memtable其實(shí)是一個(gè)SkipList,其中的記錄是根據(jù)key有序排列的,遍歷key并依次寫入一個(gè)level 0 的新建SSTable文件中,寫完后建立文件的index 數(shù)據(jù),這樣就完成了一次minor compaction。從圖中也可以看出,對(duì)于被刪除的記錄,在minor compaction過程中并不真正刪除這個(gè)記錄,原因也很簡(jiǎn)單,這里只知道要?jiǎng)h掉key記錄,但是這個(gè)KV數(shù)據(jù)在哪里?那需要復(fù)雜的查找,所以在minor compaction的時(shí)候并不做刪除,只是將這個(gè)key作為一個(gè)記錄寫入文件中,至于真正的刪除操作,在以后更高層級(jí)的compaction中會(huì)去做。
當(dāng)某個(gè)level下的SSTable文件數(shù)目超過一定設(shè)置值后,levelDb會(huì)從這個(gè)level的SSTable中選擇一個(gè)文件(level>0),將其和高一層級(jí)的level+1的SSTable文件合并,這就是major compaction。
我們知道在大于0的層級(jí)中,每個(gè)SSTable文件內(nèi)的Key都是由小到大有序存儲(chǔ)的,而且不同文件之間的key范圍(文件內(nèi)最小key和最大key之間)不會(huì)有任何重疊。Level 0的SSTable文件有些特殊,盡管每個(gè)文件也是根據(jù)Key由小到大排列,但是因?yàn)閘evel 0的文件是通過minor compaction直接生成的,所以任意兩個(gè)level 0下的兩個(gè)sstable文件可能再key范圍上有重疊。所以在做major compaction的時(shí)候,對(duì)于大于level 0的層級(jí),選擇其中一個(gè)文件就行,但是對(duì)于level 0來說,指定某個(gè)文件后,本level中很可能有其他SSTable文件的key范圍和這個(gè)文件有重疊,這種情況下,要找出所有有重疊的文件和level 1的文件進(jìn)行合并,即level 0在進(jìn)行文件選擇的時(shí)候,可能會(huì)有多個(gè)文件參與major compaction。
LevelDb在選定某個(gè)level進(jìn)行compaction后,還要選擇是具體哪個(gè)文件要進(jìn)行compaction,比如這次是文件A進(jìn)行compaction,那么下次就是在key range上緊挨著文件A的文件B進(jìn)行compaction,這樣每個(gè)文件都會(huì)有機(jī)會(huì)輪流和高層的level 文件進(jìn)行合并。
如果選好了level L的文件A和level L+1層的文件進(jìn)行合并,那么問題又來了,應(yīng)該選擇level L+1哪些文件進(jìn)行合并?levelDb選擇L+1層中和文件A在key range上有重疊的所有文件來和文件A進(jìn)行合并。也就是說,選定了level L的文件A,之后在level L+1中找到了所有需要合并的文件B,C,D…..等等。剩下的問題就是具體是如何進(jìn)行major 合并的?就是說給定了一系列文件,每個(gè)文件內(nèi)部是key有序的,如何對(duì)這些文件進(jìn)行合并,使得新生成的文件仍然Key有序,同時(shí)拋掉哪些不再有價(jià)值的KV 數(shù)據(jù)。
?
Major compaction的過程如下:對(duì)多個(gè)文件采用多路歸并排序的方式,依次找出其中最小的Key記錄,也就是對(duì)多個(gè)文件中的所有記錄重新進(jìn)行排序。之后采取一定的標(biāo)準(zhǔn)判斷這個(gè)Key是否還需要保存,如果判斷沒有保存價(jià)值,那么直接拋掉,如果覺得還需要繼續(xù)保存,那么就將其寫入level L+1層中新生成的一個(gè)SSTable文件中。就這樣對(duì)KV數(shù)據(jù)一一處理,形成了一系列新的L+1層數(shù)據(jù)文件,之前的L層文件和L+1層參與compaction 的文件數(shù)據(jù)此時(shí)已經(jīng)沒有意義了,所以全部刪除。這樣就完成了L層和L+1層文件記錄的合并過程。
那么在major compaction過程中,判斷一個(gè)KV記錄是否拋棄的標(biāo)準(zhǔn)是什么呢?其中一個(gè)標(biāo)準(zhǔn)是:對(duì)于某個(gè)key來說,如果在小于L層中存在這個(gè)Key,那么這個(gè)KV在major compaction過程中可以拋掉。因?yàn)槲覀兦懊娣治鲞^,對(duì)于層級(jí)低于L的文件中如果存在同一Key的記錄,那么說明對(duì)于Key來說,有更新鮮的Value存在,那么過去的Value就等于沒有意義了,所以可以刪除。
?
?
?
?
四、Cache
前面講過對(duì)于levelDb來說,讀取操作如果沒有在內(nèi)存的memtable中找到記錄,要多次進(jìn)行磁盤訪問操作。假設(shè)最優(yōu)情況,即第一次就在level 0中最新的文件中找到了這個(gè)key,那么也需要讀取2次磁盤,一次是將SSTable的文件中的index部分讀入內(nèi)存,這樣根據(jù)這個(gè)index可以確定key是在哪個(gè)block中存儲(chǔ);第二次是讀入這個(gè)block的內(nèi)容,然后在內(nèi)存中查找key對(duì)應(yīng)的value。
LevelDb中引入了兩個(gè)不同的Cache:Table Cache和Block Cache。其中Block Cache是配置可選的,即在配置文件中指定是否打開這個(gè)功能。
?
如上圖,在Table Cache中,key值是SSTable的文件名稱,Value部分包含兩部分,一個(gè)是指向磁盤打開的SSTable文件的文件指針,這是為了方便讀取內(nèi)容;另外一個(gè)是指向內(nèi)存中這個(gè)SSTable文件對(duì)應(yīng)的Table結(jié)構(gòu)指針,table結(jié)構(gòu)在內(nèi)存中,保存了SSTable的index內(nèi)容以及用來指示block cache用的cache_id ,當(dāng)然除此外還有其它一些內(nèi)容。
比如在get(key)讀取操作中,如果levelDb確定了key在某個(gè)level下某個(gè)文件A的key range范圍內(nèi),那么需要判斷是不是文件A真的包含這個(gè)KV。此時(shí),levelDb會(huì)首先查找Table Cache,看這個(gè)文件是否在緩存里,如果找到了,那么根據(jù)index部分就可以查找是哪個(gè)block包含這個(gè)key。如果沒有在緩存中找到文件,那么打開SSTable文件,將其index部分讀入內(nèi)存,然后插入Cache里面,去index里面定位哪個(gè)block包含這個(gè)Key 。如果確定了文件哪個(gè)block包含這個(gè)key,那么需要讀入block內(nèi)容,這是第二次讀取。
?
?
Block Cache是為了加快這個(gè)過程的,其中的key是文件的cache_id加上這個(gè)block在文件中的起始位置block_offset。而value則是這個(gè)Block的內(nèi)容。
如果levelDb發(fā)現(xiàn)這個(gè)block在block cache中,那么可以避免讀取數(shù)據(jù),直接在cache里的block內(nèi)容里面查找key的value就行,如果沒找到呢?那么讀入block內(nèi)容并把它插入block cache中。levelDb就是這樣通過兩個(gè)cache來加快讀取速度的。從這里可以看出,如果讀取的數(shù)據(jù)局部性比較好,也就是說要讀的數(shù)據(jù)大部分在cache里面都能讀到,那么讀取效率應(yīng)該還是很高的,而如果是對(duì)key進(jìn)行順序讀取效率也應(yīng)該不錯(cuò),因?yàn)橐淮巫x入后可以多次被復(fù)用。但是如果是隨機(jī)讀取,您可以推斷下其效率如何。
?
?
?
五、版本控制
在Leveldb中,Version就代表了一個(gè)版本,它包括當(dāng)前磁盤及內(nèi)存中的所有文件信息。在所有的version中,只有一個(gè)是CURRENT(當(dāng)前版本),其它都是歷史版本。
當(dāng)執(zhí)行一次compaction 或者 創(chuàng)建一個(gè)Iterator后,Leveldb將在當(dāng)前版本基礎(chǔ)上創(chuàng)建一個(gè)新版本,當(dāng)前版本就變成了歷史版本。
?
VersionSet?是所有Version的集合,管理著所有存活的Version。
VersionEdit?表示Version之間的變化,相當(dāng)于delta 增量,表示有增加了多少文件,刪除了文件:
Version0 + VersionEdit --> Version1 Version0->Version1->Version2->Version3?
VersionEdit會(huì)保存到MANIFEST文件中,當(dāng)做數(shù)據(jù)恢復(fù)時(shí)就會(huì)從MANIFEST文件中讀出來重建數(shù)據(jù)。
Leveldb的這種版本的控制,讓我想到了雙buffer切換,雙buffer切換來自于圖形學(xué)中,用于解決屏幕繪制時(shí)的閃屏問題,在服務(wù)器編程中也有用處。
比如我們的服務(wù)器上有一個(gè)字典庫(kù),每天我們需要更新這個(gè)字典庫(kù),我們可以新開一個(gè)buffer,將新的字典庫(kù)加載到這個(gè)新buffer中,等到加載完畢,將字典的指針指向新的字典庫(kù)。
Leveldb的version管理和雙buffer切換類似,但是如果原version被某個(gè)iterator引用,那么這個(gè)version會(huì)一直保持,直到?jīng)]有被任何一個(gè)iterator引用,此時(shí)就可以刪除這個(gè)version。
?
學(xué)習(xí)參看
http://blog.csdn.net/qq112928/article/details/21172841
http://www.cnblogs.com/chenny7/p/4026447.html
http://www.cnblogs.com/haippy/archive/2011/12/04/2276064.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
- 上一篇: tomcat web.xml配置
- 下一篇: ES 在数据量很大的情况下如何提高查询效