LevelDb 詳解

LevelDb日知錄之一LevelDb 101

  說起LevelDb也許您不清楚但是如果作爲IT工程師不知道下面兩位大神級別的工程師那您的領導估計會Hold不住了Jeff DeanSanjay Ghemawat。這兩位是Google公司重量級的工程師爲數甚少的Google Fellow之二

  Jeff Dean其人http://research.google.com/people/jeff/index.html,Google大規模分佈式平臺BigtableMapReduce主要設計和實現者

  Sanjay Ghemawat其人http://research.google.com/people/sanjay/index.html,Google大規模分佈式平臺GFS,BigtableMapReduce主要設計和實現工程師

  LevelDb就是這兩位大神級別的工程師發起的開源項目簡而言之LevelDb是能夠處理十億級別規模Key-Value型數據持久性存儲的C++ 程序庫正像上面介紹的這二位是Bigtable的設計和實現者如果瞭解Bigtable的話應該知道在這個影響深遠的分佈式存儲系統中有兩個核心的部分Master ServerTablet Server。其中Master Server做一些管理數據的存儲以及分佈式調度工作實際的分佈式數據存儲以及讀寫操作是由Tablet Server完成的LevelDb則可以理解爲一個簡化版的Tablet Server。

  LevelDb有如下一些特點

    首先LevelDb是一個持久化存儲的KV系統Redis這種內存型的KV系統不同LevelDb不會像Redis一樣狂喫內存而是將大部分數據存儲到磁盤上

    其次LevleDb在存儲數據時是根據記錄的key值有序存儲的就是說相鄰的key值在存儲文件中是依次順序存儲的而應用可以自定義key大小比較函數LevleDb會按照用戶定義的比較函數依序存儲這些記錄

    再次像大多數KV系統一樣LevelDb的操作接口很簡單基本操作包括寫記錄讀記錄以及刪除記錄也支持針對多條操作的原子批量操作

    另外LevelDb支持數據快照snapshot)功能使得讀取操作不受寫操作影響可以在讀操作過程中始終看到一致的數據

  除此外LevelDb還支持數據壓縮等操作這對於減小存儲空間以及增快IO效率都有直接的幫助

  LevelDb性能非常突出官方網站報道其隨機寫性能達到40萬條記錄每秒而隨機讀性能達到6萬條記錄每秒總體來說LevelDb的寫操作要大大快於讀操作而順序讀寫操作則大大快於隨機讀寫操作至於爲何是這樣看了我們後續推出的LevelDb日知錄估計您會了解其內在原因

LevelDb日知錄之二:整體架構

      LevelDb本質上是一套存儲系統以及在這套存儲系統上提供的一些操作接口爲了便於理解整個系統及其處理流程我們可以從兩個不同的角度來看待LevleDb:靜態角度和動態角度從靜態角度可以假想整個系統正在運行過程中不斷插入刪除讀取數據),此時我們給LevelDb照相從照片可以看到之前系統的數據在內存和磁盤中是如何分佈的處於什麼狀態等從動態的角度主要是瞭解系統是如何寫入一條記錄讀出一條記錄刪除一條記錄的同時也包括除了這些接口操作外的內部操作比如compaction,系統運行時崩潰後如何恢復系統等等方面

     本節所講的整體架構主要從靜態角度來描述之後接下來的幾節內容會詳述靜態結構涉及到的文件或者內存數據結構LevelDb日知錄後半部分主要介紹動態視角下的LevelDb,就是說整個系統是怎麼運轉起來的

     LevelDb作爲存儲系統數據記錄的存儲介質包括內存以及磁盤文件如果像上面說的LevelDb運行了一段時間此時我們給LevelDb進行透視拍照那麼您會看到如下一番景象

1.1:LevelDb結構

    從圖中可以看出構成LevelDb靜態結構的包括六個主要部分內存中的MemTableImmutable MemTable以及磁盤上的幾種主要文件Current文件Manifest文件log文件以及SSTable文件當然LevelDb除了這六個主要部分還有一些輔助的文件但是以上六個文件和數據結構是LevelDb的主體構成元素

LevelDbLog文件和MemtableBigtable論文中介紹的是一致的當應用寫入一條Key:Value記錄的時候LevelDb會先往log文件裏寫入成功後將記錄插進Memtable這樣基本就算完成了寫入操作因爲一次寫入操作只涉及一次磁盤順序寫和一次內存寫入所以這是爲何說LevelDb寫入速度極快的主要原因

Log文件在系統中的作用主要是用於系統崩潰恢復而不丟失數據假如沒有Log文件因爲寫入的記錄剛開始是保存在內存中的此時如果系統崩潰內存中的數據還沒有來得及Dump到磁盤所以會丟失數據Redis就存在這個問題)。爲了避免這種情況LevelDb在寫入內存前先將操作記錄到Log文件中然後再記入內存中這樣即使系統崩潰也可以從Log文件中恢復內存中的Memtable,不會造成數據的丟失

Memtable插入的數據佔用內存到了一個界限後需要將內存的記錄導出到外存文件中LevleDb會生成新的Log文件和Memtable,原先的Memtable就成爲Immutable Memtable,顧名思義就是說這個Memtable的內容是不可更改的只能讀不能寫入或者刪除新到來的數據被記入新的Log文件和Memtable,LevelDb後臺調度會將Immutable Memtable的數據導出到磁盤形成一個新的SSTable文件SSTable就是由內存中的數據不斷導出並進行Compaction操作後形成的而且SSTable的所有文件是一種層級結構第一層爲Level 0,第二層爲Level 1,依次類推層級逐漸增高這也是爲何稱之爲LevelDb的原因

    SSTable中的文件是Key有序的就是說在文件中小key記錄排在大Key記錄之前各個LevelSSTable都是如此但是這裏需要注意的一點是Level 0SSTable文件後綴爲.sst)和其它Level的文件相比有特殊性這個層級內的.sst文件兩個文件可能存在key重疊比如有兩個level 0sst文件文件A和文件B,文件Akey範圍是:{bar, car},文件BKey範圍是{blue,samecity},那麼很可能兩個文件都存在key=”blood”的記錄對於其它LevelSSTable文件來說則不會出現同一層級內.sst文件的key重疊現象就是說Level L中任意兩個.sst文件那麼可以保證它們的key值是不會重疊的這點需要特別注意後面您會看到很多操作的差異都是由於這個原因造成的

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

2.1:Manifest存儲示意圖

圖中只顯示了兩個文件manifest會記載所有SSTable文件的這些信息),Level 0test.sst1test.sst2文件同時記載了這些文件各自對應的key範圍比如test.sstt1key範圍是an” “banana”,而文件test.sst2key範圍是baby”samecity”,可以看出兩者的key範圍是有重疊的

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

以上介紹的內容就構成了LevelDb的整體靜態結構LevelDb日知錄接下來的內容中我們會首先介紹重要文件或者內存數據的具體數據佈局與結構

LevelDb日知錄之三:log文件

     上節內容講到log文件在LevelDb中的主要作用是系統故障恢復時能夠保證不會丟失數據因爲在將記錄寫入內存的Memtable之前會先寫入Log文件這樣即使系統發生故障Memtable中的數據沒有來得及Dump到磁盤的SSTable文件LevelDB也可以根據log文件恢復內存的Memtable數據結構內容不會造成系統丟失數據在這點上LevelDbBigtable是一致的

     下面我們帶大家看看log文件的具體物理和邏輯佈局是怎樣的LevelDb對於一個log文件會把它切割成以32K爲單位的物理Block,每次讀取的單位以一個Block作爲基本讀取單位下圖展示的log文件由3Block構成所以從物理佈局來講一個log文件就是由連續的32K大小Block構成的

3.1 log文件佈局

        在應用的視野裏是看不到這些Block應用看到的是一系列的Key:ValueLevelDb內部會將一個Key:Value對看做一條記錄的數據另外在這個數據前增加一個記錄頭用來記載一些管理信息以方便內部處理3.2顯示了一個記錄在LevelDb內部是如何表示的

 

3.2 記錄結構

       記錄頭包含三個字段ChechSum是對類型數據字段的校驗碼爲了避免處理不完整或者是被破壞的數據LevelDb讀取記錄數據時候會對數據進行校驗如果發現和存儲的CheckSum相同說明數據完整無破壞可以繼續後續流程。“記錄長度記載了數據的大小,“數據則是上面講的Key:Value數值對,“類型字段則指出了每條記錄的邏輯結構和log文件物理分塊結構之間的關係具體而言主要有以下四種類型FULL/FIRST/MIDDLE/LAST。

        如果記錄類型是FULL,代表了當前記錄內容完整地存儲在一個物理Block沒有被不同的物理Block切割開如果記錄被相鄰的物理Block切割開則類型會是其他三種類型中的一種我們以圖3.1所示的例子來具體說明

       假設目前存在三條記錄Record A,Record BRecord C,其中Record A大小爲10K,Record B 大小爲80K,Record C大小爲12K,那麼其在log文件中的邏輯佈局會如圖3.1所示Record A是圖中藍色區域所示因爲大小爲10K<32K,能夠放在一個物理Block所以其類型爲FULL;Record B 大小爲80K,Block 1因爲放入了Record A,所以還剩下22K,不足以放下Record B,所以在Block 1的剩餘部分放入Record B的開頭一部分類型標識爲FIRST,代表了是一個記錄的起始部分Record B還有58K沒有存儲這些只能依次放在後續的物理Block裏面因爲Block 2大小隻有32K,仍然放不下Record B的剩餘部分所以Block 2全部用來放Record B,且標識類型爲MIDDLE,意思是這是Record B中間一段數據Record B剩下的部分可以完全放在Block 3類型標識爲LAST,代表了這是Record B的末尾數據圖中黃色的Record C因爲大小爲12K,Block 3剩下的空間足以全部放下它所以其類型標識爲FULL。

     從這個小例子可以看出邏輯記錄和物理Block之間的關係LevelDb一次物理讀取爲一個Block,然後根據類型情況拼接出邏輯記錄供後續流程處理

LevelDb日知錄之四:SSTable文件

   SSTableBigtable中至關重要的一塊對於LevelDb來說也是如此LevelDbSSTable實現細節的瞭解也有助於瞭解Bigtable中一些實現細節

本節內容主要講述SSTable的靜態佈局結構我們曾在LevelDb日知錄之二整體架構中說過SSTable文件形成了不同Level的層級結構至於這個層級結構是如何形成的我們放在後面Compaction一節細說本節主要介紹SSTable某個文件的物理佈局和邏輯佈局結構這對了解LevelDb的運行過程很有幫助

  LevelDb不同層級有很多SSTable文件以後綴.sst爲特徵),所有.sst文件內部佈局都是一樣的上節介紹Log文件是物理分塊的SSTable也一樣會將文件劃分爲固定大小的物理存儲塊但是兩者邏輯佈局大不相同根本原因是Log文件中的記錄是Key無序的即先後記錄的key大小沒有明確大小關係.sst文件內部則是根據記錄的Key由小到大排列的從下面介紹的SSTable佈局可以體會到Key有序是爲何如此設計.sst文件結構的關鍵

4.1 .sst文件的分塊結構

  4.1展示了一個.sst文件的物理劃分結構Log文件一樣也是劃分爲固定大小的存儲塊每個Block分爲三個部分紅色部分是數據存儲區, 藍色的Type區用於標識數據存儲區是否採用了數據壓縮算法Snappy壓縮或者無壓縮兩種),CRC部分則是數據校驗碼用於判別數據是否在生成和傳輸中出錯

  以上是.sst的物理佈局下面介紹.sst文件的邏輯佈局所謂邏輯佈局就是說盡管大家都是物理塊但是每一塊存儲什麼內容內部又有什麼結構等4.2展示了.sst文件的內部邏輯解釋

4.2 邏輯佈局

  從圖4.2可以看出從大的方面可以將.sst文件劃分爲數據存儲區和數據管理區數據存儲區存放實際的Key:Value數據數據管理區則提供一些索引指針等管理數據目的是更快速便捷的查找相應的記錄兩個區域都是在上述的分塊基礎上的就是說文件的前面若干塊實際存儲KV數據後面數據管理區存儲管理數據管理數據又分爲四種不同類型紫色的Meta Block,紅色的MetaBlock 索引和藍色的數據索引塊以及一個文件尾部塊

  LevelDb 1.2版對於Meta Block尚無實際使用只是保留了一個接口估計會在後續版本中加入內容下面我們看看數據索引區和文件尾部Footer的內部結構

4.3 數據索引

  圖4.3是數據索引的內部結構示意圖再次強調一下Data Block內的KV記錄是按照Key由小到大排列的數據索引區的每條記錄是對某個Data Block建立的索引信息每條索引信息包含三個內容以圖4.3所示的數據塊i的索引Index i來說紅色部分的第一個字段記載大於等於數據塊i中最大的Key值的那個Key,第二個字段指出數據塊i.sst文件中的起始位置第三個字段指出Data Block i的大小有時候是有數據壓縮的)。後面兩個字段好理解是用於定位數據塊在文件中的位置的第一個字段需要詳細解釋一下在索引裏保存的這個Key值未必一定是某條記錄的Key,以圖4.3的例子來說假設數據塊的最小Key=“samecity”,最大Key=“the best”;數據塊i+1的最小Key=“the fox”,最大Key=“zoo”,那麼對於數據塊i的索引Index i來說其第一個字段記載大於等於數據塊i的最大Key(“the best”)同時要小於數據塊i+1的最小Key(“the fox”),所以例子中Index i的第一個字段是:“the c”,這個是滿足要求的Index i+1的第一個字段則是zoo”,即數據塊i+1的最大Key。

  文件末尾Footer塊的內部結構見圖4.4,metaindex_handle指出了metaindex block的起始位置和大小inex_handle指出了index Block的起始地址和大小這兩個字段可以理解爲索引的索引是爲了正確讀出索引值而設立的後面跟着一個填充區和魔數

4.4 Footer

  上面主要介紹的是數據管理區的內部結構下面我們看看數據區的一個Block的數據部分內部是如何佈局的4.1中的紅色部分),4.5是其內部佈局示意圖

4.5 數據Block內部結構

  從圖中可以看出其內部也分爲兩個部分前面是一個個KV記錄其順序是根據Key值由小到大排列的Block尾部則是一些重啓點”(Restart Point),其實是一些指針指出Block內容中的一些記錄位置

  “重啓點是幹什麼的呢我們一再強調Block內容裏的KV記錄是按照Key大小有序的這樣的話相鄰的兩條記錄很可能Key部分存在重疊比如key i=“the Car”,Key i+1=“the color”,那麼兩者存在重疊部分the c”,爲了減少Key的存儲量Key i+1可以只存儲和上一條Key不同的部分olor”,兩者的共同部分從Key i中可以獲得記錄的KeyBlock內容部分就是這麼存儲的主要目的是減少存儲開銷。“重啓點的意思是在這條記錄開始不再採取只記載不同的Key部分而是重新記錄所有的Key假設Key i+1是一個重啓點那麼Key裏面會完整存儲the color”,而不是採用簡略的olor”方式Block尾部就是指出哪些記錄是這些重啓點的

4.6 記錄格式

  在Block內容區每個KV記錄的內部結構是怎樣的4.6給出了其詳細結構每個記錄包含5個字段key共享長度比如上面的olor”記錄, key和上一條記錄共享的Key部分長度是the c”的長度5;key非共享長度對於olor”來說4;value長度指出Key:ValueValue的長度在後面的Value內容字段中存儲實際的Valuekey非共享內容則實際存儲olor”這個Key字符串

  上面講的這些就是.sst文件的全部內部奧祕

LevelDb日知錄之五:MemTable詳解

   LevelDb日知錄前述小節大致講述了磁盤文件相關的重要靜態結構本小節講述內存中的數據結構Memtable,Memtable在整個體系中的重要地位也不言而喻總體而言所有KV數據都是存儲在Memtable,Immutable MemtableSSTable中的Immutable Memtable從結構上講和Memtable是完全一樣的區別僅僅在於其是隻讀的不允許寫入操作Memtable則是允許寫入和讀取的Memtable寫入的數據佔用內存到達指定數量則自動轉換爲Immutable Memtable,等待Dump到磁盤中系統會自動生成新的Memtable供寫操作寫入新數據理解了Memtable,那麼Immutable Memtable自然不在話下

   LevelDbMemTable提供了將KV數據寫入刪除以及讀取KV記錄的操作接口但是事實上Memtable並不存在真正的刪除操作,刪除某個KeyValueMemtable內是作爲插入一條記錄實施的但是會打上一個Key的刪除標記真正的刪除操作是Lazy會在以後的Compaction過程中去掉這個KV。

   需要注意的是LevelDbMemtableKV對是根據Key大小有序存儲的在系統插入新的KVLevelDb要把這個KV插到合適的位置上以保持這種Key有序性其實LevelDbMemtable類只是一個接口類真正的操作是通過背後的SkipList來做的包括插入操作和讀取操作等所以Memtable的核心數據結構是一個SkipList。

   SkipList是由William Pugh發明他在Communications of the ACM June 1990, 33(6) 668-676 發表了Skip lists: a probabilistic alternative to balanced trees,在該論文中詳細解釋了SkipList的數據結構和插入刪除操作

SkipList是平衡樹的一種替代數據結構但是和紅黑樹不相同的是SkipList對於樹的平衡的實現是基於一種隨機化的算法的這樣也就是說SkipList的插入和刪除的工作是比較簡單的

關於SkipList的詳細介紹可以參考這篇文章http://www.cnblogs.com/xuqiang/archive/2011/05/22/2053516.html講述的很清楚LevelDbSkipList基本上是一個具體實現並無特殊之處

  SkipList不僅是維護有序數據的一個簡單實現而且相比較平衡樹來說在插入數據的時候可以避免頻繁的樹節點調整操作所以寫入效率是很高的LevelDb整體而言是個高寫入系統SkipList在其中應該也起到了很重要的作用Redis爲了加快插入操作也使用了SkipList來作爲內部實現數據結構

LevelDb日知錄之六 寫入與刪除記錄

      在之前的五節LevelDb日知錄中我們介紹了LevelDb的一些靜態文件及其詳細布局從本節開始我們看看LevelDb的一些動態操作比如讀寫記錄Compaction,錯誤恢復等操作

       本節介紹levelDb的記錄更新操作即插入一條KV記錄或者刪除一條KV記錄levelDb的更新操作速度是非常快的源於其內部機制決定了這種更新操作的簡單性 

6.1 LevelDb寫入記錄

        6.1levelDb如何更新KV數據的示意圖從圖中可以看出對於一個插入操作Put(Key,Value)來說完成插入操作包含兩個具體步驟首先是將這條KV記錄以順序寫的方式追加到之前介紹過的log文件末尾因爲儘管這是一個磁盤讀寫操作但是文件的順序追加寫入效率是很高的所以並不會導致寫入速度的降低第二個步驟是:如果寫入log文件成功那麼將這條KV記錄插入內存中的Memtable前面介紹過Memtable只是一層封裝其內部其實是一個Key有序的SkipList列表插入一條新記錄的過程也很簡單即先查找合適的插入位置然後修改相應的鏈接指針將新記錄插入即可完成這一步寫入記錄就算完成了所以一個插入記錄操作涉及一次磁盤文件追加寫和內存SkipList插入操作這是爲何levelDb寫入速度如此高效的根本原因

        從上面的介紹過程中也可以看出log文件內是key無序的Memtable中是key有序的那麼如果是刪除一條KV記錄呢對於levelDb來說並不存在立即刪除的操作而是與插入操作相同的區別是插入操作插入的是Key:Value 而刪除操作插入的是Key:刪除標記”,並不真正去刪除記錄而是後臺Compaction的時候纔去做真正的刪除操作

        levelDb的寫入操作就是如此簡單真正的麻煩在後面將要介紹的讀取操作中

LevelDb日知錄之七:讀取記錄

   LevelDb是針對大規模Key/Value數據的單機存儲庫從應用的角度來看LevelDb就是一個存儲工具而作爲稱職的存儲工具常見的調用接口無非是新增KV,刪除KV,讀取KV,更新Key對應的Value值這麼幾種操作LevelDb的接口沒有直接支持更新操作的接口如果需要更新某個KeyValue,你可以選擇直接生猛地插入新的KV,保持Key相同這樣系統內的key對應的value就會被更新或者你可以先刪除舊的KV, 之後再插入新的KV,這樣比較委婉地完成KV的更新操作

     假設應用提交一個Key下面我們看看LevelDb是如何從存儲的數據中讀出其對應的Value值的7-1LevelDb讀取過程的整體示意圖

7-1  LevelDb讀取記錄流程

  LevelDb首先會去查看內存中的Memtable,如果Memtable中包含key及其對應的value,則返回value值即可如果在Memtable沒有讀到key,則接下來到同樣處於內存中的Immutable Memtable中去讀取類似地如果讀到就返回若是沒有讀到,那麼只能萬般無奈下從磁盤中的大量SSTable文件中查找因爲SSTable數量較多而且分成多個Level,所以在SSTable中讀數據是相當蜿蜒曲折的一段旅程總的讀取原則是這樣的首先從屬於level 0的文件中查找如果找到則返回對應的value如果沒有找到那麼到level 1中的文件中去找如此循環往復直到在某層SSTable文件中找到這個key對應的value爲止或者查到最高level,查找失敗說明整個系統中不存在這個Key)。

   那麼爲什麼是從MemtableImmutable Memtable,再從Immutable Memtable到文件而文件中爲何是從低level到高level這麼一個查詢路徑呢道理何在之所以選擇這麼個查詢路徑是因爲從信息的更新時間來說很明顯Memtable存儲的是最新鮮的KVImmutable Memtable中存儲的KV數據對的新鮮程度次之而所有SSTable文件中的KV數據新鮮程度一定不如內存中的MemtableImmutable Memtable對於SSTable文件來說如果同時在level LLevel L+1找到同一個key,level L的信息一定比level L+1的要新也就是說上面列出的查找路徑就是按照數據新鮮程度排列出來的越新鮮的越先查找

   爲啥要優先查找新鮮的數據呢這個道理不言而喻舉個例子比如我們先往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 LLevel L+1找到同一個key,level L的信息一定比level L+1的要新這是一個結論理論上需要一個證明過程否則會招致如下的問題爲神馬呢從道理上講呢很明白因爲Level L+1的數據不是從石頭縫裏蹦出來的也不是做夢夢到的那它是從哪裏來的Level L+1的數據是從Level L 經過Compaction後得到的如果您不知道什麼是Compaction,那麼........也許以後會知道的),也就是說您看到的現在的Level L+1層的SSTable數據是從原來的Level L中來的現在的Level L比原來的Level L數據要新鮮所以可證現在的Level L比現在的Level L+1的數據要新鮮

  SSTable文件很多如何快速地找到key對應的valueLevelDblevel 0一直都愛搞特殊化level 0和其它level中查找某個key的過程是不一樣的因爲level 0下的不同文件可能key的範圍有重疊某個要查詢的key有可能多個文件都包含這樣的話LevelDb的策略是先找出level 0中哪些文件包含這個key(manifest文件中記載了level和對應的文件及文件裏key的範圍信息LevelDb在內存中保留這種映射表), 之後按照文件的新鮮程度排序新的文件排在前面之後依次查找讀出key對應的value。而如果是非level 0的話因爲這個level的文件之間key是不重疊的所以只從一個文件就可以找到key對應的value。

  最後一個問題,如果給定一個要查詢的key和某個key range包含這個keySSTable文件那麼levelDb是如何進行具體查找過程的呢levelDb一般會先在內存中的Cache中查找是否包含這個文件的緩存記錄如果包含則從緩存中讀取如果不包含則打開SSTable文件同時將這個文件的索引部分加載到內存中並放入Cache。 這樣Cache裏面就有了這個SSTable的緩存項但是隻有索引部分在內存中之後levelDb根據索引可以定位到哪個內容Block會包含這條key,從文件中讀出這個Block的內容在根據記錄一一比較如果找到則返回結果如果沒有找到那麼說明這個levelSSTable文件並不包含這個key,所以到下一級別的SSTable中去查找

  從之前介紹的LevelDb的寫操作和這裏介紹的讀操作可以看出相對寫操作讀操作處理起來要複雜很多所以寫的速度必然要遠遠高於讀數據的速度也就是說LevelDb比較適合寫操作多於讀操作的應用場合而如果應用是很多讀操作類型的那麼順序讀取效率會比較高因爲這樣大部分內容都會在緩存中找到儘可能避免大量的隨機讀取操作

LevelDb日知錄之八:Compaction操作

     前文有述對於LevelDb來說寫入記錄操作很簡單刪除記錄僅僅寫入一個刪除標記就算完事但是讀取記錄比較複雜需要在內存以及各個層級文件中依照新鮮程度依次查找代價很高爲了加快讀取速度levelDb採取了compaction的方式來對已有的記錄進行整理壓縮通過這種方式來刪除掉一些不再有效的KV數據減小數據規模減少文件數量等

     levelDbcompaction機制和過程與Bigtable所講述的是基本一致的Bigtable中講到三種類型的compaction: minor ,majorfull。所謂minor Compaction,就是把memtable中的數據導出到SSTable文件中major compaction就是合併不同層級的SSTable文件full compaction就是將所有SSTable進行合併

     LevelDb包含其中兩種minormajor。

    我們將爲大家詳細敘述其機理

    先來看看minor Compaction的過程Minor compaction 的目的是當內存中的memtable大小到了一定值時將內容保存到磁盤文件中8.1是其機理示意圖 

8.1 minor compaction

     8.1可以看出memtable數量到了一定程度會轉換爲immutable memtable,此時不能往其中寫入記錄只能從中讀取KV內容之前介紹過immutable memtable其實是一個多層級隊列SkipList,其中的記錄是根據key有序排列的所以這個minor compaction實現起來也很簡單就是按照immutable memtable中記錄由小到大遍歷並依次寫入一個level 0 的新建SSTable文件中寫完後建立文件的index 數據這樣就完成了一次minor compaction。從圖中也可以看出對於被刪除的記錄minor compaction過程中並不真正刪除這個記錄原因也很簡單這裏只知道要刪掉key記錄但是這個KV數據在哪裏?那需要複雜的查找所以在minor compaction的時候並不做刪除只是將這個key作爲一個記錄寫入文件中至於真正的刪除操作在以後更高層級的compaction中會去做

     當某個level下的SSTable文件數目超過一定設置值後levelDb會從這個levelSSTable中選擇一個文件level>0),將其和高一層級的level+1SSTable文件合併這就是major compaction。

    我們知道在大於0的層級中每個SSTable文件內的Key都是由小到大有序存儲的而且不同文件之間的key範圍文件內最小key和最大key之間不會有任何重疊Level 0SSTable文件有些特殊儘管每個文件也是根據Key由小到大排列但是因爲level 0的文件是通過minor compaction直接生成的所以任意兩個level 0下的兩個sstable文件可能再key範圍上有重疊所以在做major compaction的時候對於大於level 0的層級選擇其中一個文件就行但是對於level 0來說指定某個文件後level中很可能有其他SSTable文件的key範圍和這個文件有重疊這種情況下要找出所有有重疊的文件和level 1的文件進行合併level 0在進行文件選擇的時候可能會有多個文件參與major compaction。

  levelDb在選定某個level進行compaction還要選擇是具體哪個文件要進行compaction,levelDb在這裏有個小技巧, 就是說輪流來比如這次是文件A進行compaction,那麼下次就是在key range上緊挨着文件A的文件B進行compaction,這樣每個文件都會有機會輪流和高層的level 文件進行合併

如果選好了level L的文件Alevel L+1層的文件進行合併那麼問題又來了應該選擇level L+1哪些文件進行合併levelDb選擇L+1層中和文件Akey range上有重疊的所有文件來和文件A進行合併

   也就是說選定了level L的文件A,之後在level L+1中找到了所有需要合併的文件B,C,D…..等等剩下的問題就是具體是如何進行major 合併的就是說給定了一系列文件每個文件內部是key有序的如何對這些文件進行合併使得新生成的文件仍然Key有序同時拋掉哪些不再有價值的KV 數據

    8.2說明了這一過程

8.2 SSTable Compaction

  Major compaction的過程如下對多個文件採用多路歸併排序的方式依次找出其中最小的Key記錄也就是對多個文件中的所有記錄重新進行排序之後採取一定的標準判斷這個Key是否還需要保存如果判斷沒有保存價值那麼直接拋掉如果覺得還需要繼續保存那麼就將其寫入level L+1層中新生成的一個SSTable文件中就這樣對KV數據一一處理形成了一系列新的L+1層數據文件之前的L層文件和L+1層參與compaction 的文件數據此時已經沒有意義了所以全部刪除這樣就完成了L層和L+1層文件記錄的合併過程

  那麼在major compaction過程中判斷一個KV記錄是否拋棄的標準是什麼呢其中一個標準是:對於某個key來說如果在小於L層中存在這個Key,那麼這個KVmajor compaction過程中可以拋掉因爲我們前面分析過對於層級低於L的文件中如果存在同一Key的記錄那麼說明對於Key來說有更新鮮的Value存在那麼過去的Value就等於沒有意義了所以可以刪除


LevelDb日知錄之九 levelDb中的Cache

  書接前文前面講過對於levelDb來說讀取操作如果沒有在內存的memtable中找到記錄要多次進行磁盤訪問操作假設最優情況即第一次就在level 0中最新的文件中找到了這個key,那麼也需要讀取2次磁盤一次是將SSTable的文件中的index部分讀入內存這樣根據這個index可以確定key是在哪個block中存儲第二次是讀入這個block的內容然後在內存中查找key對應的value。

  levelDb中引入了兩個不同的Cache:Table CacheBlock Cache。其中Block Cache是配置可選的即在配置文件中指定是否打開這個功能

9.1 table cache

   9.1table cache的結構Cachekey值是SSTable的文件名稱Value部分包含兩部分一個是指向磁盤打開的SSTable文件的文件指針這是爲了方便讀取內容另外一個是指向內存中這個SSTable文件對應的Table結構指針table結構在內存中保存了SSTableindex內容以及用來指示block cache用的cache_id ,當然除此外還有其它一些內容

  比如在get(key)讀取操作中如果levelDb確定了key在某個level下某個文件Akey range範圍內那麼需要判斷是不是文件A真的包含這個KV。此時levelDb會首先查找Table Cache,看這個文件是否在緩存裏如果找到了那麼根據index部分就可以查找是哪個block包含這個key。如果沒有在緩存中找到文件那麼打開SSTable文件將其index部分讀入內存然後插入Cache裏面index裏面定位哪個block包含這個Key 。如果確定了文件哪個block包含這個key,那麼需要讀入block內容這是第二次讀取

9.2 block cache

   Block Cache是爲了加快這個過程的9.2是其結構示意圖其中的key是文件的cache_id加上這個block在文件中的起始位置block_offset。value則是這個Block的內容

  如果levelDb發現這個blockblock cache那麼可以避免讀取數據直接在cache裏的block內容裏面查找keyvalue就行如果沒找到呢那麼讀入block內容並把它插入block cachelevelDb就是這樣通過兩個cache來加快讀取速度的從這裏可以看出如果讀取的數據局部性比較好也就是說要讀的數據大部分在cache裏面都能讀到那麼讀取效率應該還是很高的而如果是對key進行順序讀取效率也應該不錯因爲一次讀入後可以多次被複用但是如果是隨機讀取您可以推斷下其效率如何

LevelDb日知錄之十 Version、VersionEdit、VersionSet

  Version 保存了當前磁盤以及內存中所有的文件信息一般只有一個Version叫做"current" version(當前版本)。Leveldb還保存了一系列的歷史版本這些歷史版本有什麼作用呢

當一個Iterator創建後Iterator就引用到了current version(當前版本),只要這個Iterator不被delete那麼被Iterator引用的版本就會一直存活這就意味着當你用完一個Iterator需要及時刪除它

  當一次Compaction結束後會生成新的文件合併前的文件需要刪除),Leveldb會創建一個新的版本作爲當前版本原先的當前版本就會變爲歷史版本

  VersionSet 是所有Version的集合管理着所有存活的Version。

  VersionEdit 表示Version之間的變化相當於delta 增量表示有增加了多少文件刪除了文件下圖表示他們之間的關係

Version+VersionEdit-->Version1

  VersionEdit會保存到MANIFEST文件中當做數據恢復時就會從MANIFEST文件中讀出來重建數據

  leveldb的這種版本的控制讓我想到了雙buffer切換buffer切換來自於圖形學中用於解決屏幕繪製時的閃屏問題在服務器編程中也有用處

  比如我們的服務器上有一個字典庫每天我們需要更新這個字典庫我們可以新開一個buffer,將新的字典庫加載到這個新buffer等到加載完畢將字典的指針指向新的字典庫

leveldbversion管理和雙buffer切換類似但是如果原version被某個iterator引用那麼這個version會一直保持直到沒有被任何一個iterator引用此時就可以刪除這個version

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章