資料庫和儲存引擎。資料庫往往是乙個比較豐富完整的系統, 提供了sql查詢語言,事務和水平擴充套件等支援。然而儲存引擎則是小而精, 純粹專注於單機的讀/寫/儲存。一般來說, 資料庫底層往往會使用某種儲存引擎。
目前開源的kv儲存引擎中,rocksdb是流行的乙個,mongodb和mysql底層可以切換成rocksdb, tidb底層直接使用了rocksdb。大多數分布式資料庫的底層不約而同的都選擇了rocksdb。
rocksdb最初是從leveldb進化而來的,我們先從簡單一點的leveldb入手,借鑑它的設計思路。
有乙個反直覺的事情是,記憶體隨機寫甚至比硬碟的順序讀還要慢,磁碟隨機寫就更慢了,說明我們要避免隨機寫,最好設計成順序寫。因此好的kv儲存引擎,都在盡量避免更新操作,把更新和刪除操作轉化為順序寫操作。leveldb採用了一種sstable的資料結構來達到這個目的。
效能對比表
順序讀順序寫
隨機讀隨機寫
記憶體optane ssd
ssdhdd
區別:硬碟,磁碟,nvme,ssd
(1)為什麼順序讀寫的速度比隨機讀寫快?這是因為機械硬碟的物理結構決定的嗎?那ssd是否存在這個問題呢?
sstable(sorted string table)就是一組按照key排序好的 key-value對, key和value都是位元組陣列。sstable既可以在記憶體中,也可以在硬碟中。sstable底層使用lsm tree(log-structured merge tree)來存放有序的key-value對。
leveldb整體由如下幾個組成部分,
memtable。即記憶體中的sstable,新資料會寫入到這裡,然後批量寫入磁碟,以此提高寫的吞吐量。
log檔案。寫memtable前會寫log檔案,即用wal(write ahead log)方式記錄日誌,如果機器突然掉電,記憶體中的memtable丟失了,還可以通過日誌恢復資料。wal日誌是很多傳統資料庫例如mysql採用的技術,詳細解釋可以參考資料庫如何用 wal 保證事務一致性? - 知乎專欄。
immutable memtable。記憶體中的memtable達到指定的大小後,將不再接收新資料,同時會有新的memtable產生,新資料寫入到這個新的memtable裡,immutable memtable隨後會寫入硬碟,變成乙個sst檔案。
sstable檔案。即硬碟上的sstable,檔案尾部追加了一塊索引,記錄key->offset,提高隨機讀的效率。sst檔案為level 0到level n多層,每一層包含多個sst檔案;單個sst檔案容量隨層次增加成倍增長;level0的sst檔案由immutable memtable直接dump產生,其他level的sst檔案由其上一層的檔案和本層檔案歸併產生。
manifest檔案。 manifest檔案中記錄sst檔案在不同level的分布,單個sst檔案的最大最小key,以及其他一些leveldb需要的元資訊。
current檔案。從上面的介紹可以看出,leveldb啟動時的首要任務就是找到當前的manifest,而manifest可能有多個。current檔案簡單的記錄了當前manifest的檔名。
leveldb的一些核心邏輯如下:
首先sst檔案尾部的索引要放在記憶體中,這樣讀索引就不需要一次磁碟io了
所有讀要先檢視memtable,如果沒有再檢視記憶體中的索引
所有寫操作只能寫到memtable, 因為sst檔案不可修改
定期把immutable memtable寫入硬碟,成為sstable檔案,同時新建乙個memtable會繼續接收新來的寫操作
定期對sstable檔案進行合併
由於硬碟上的sstable檔案是不可修改的,那怎麼更新和刪除資料呢?對於更新操作,追加乙個新的key-value對到檔案尾部,由於讀sstable檔案是從前向後讀的,所以新資料會最先被讀到;對於刪除操作,追加「墓碑」值(tombstone),表示刪除該key,在定期合併sstable檔案時丟棄這些key, 即可刪除這些key。
manifest檔案記錄各個sstable各個檔案的管理資訊,比如該sst檔案處於哪個level,檔名稱叫啥,最小key和最大key各自是多少,如下圖所示:
log檔案主要作用是系統發生故障時,能夠保證不會丟失資料。因為在資料寫入記憶體中的memtable之前,會先寫入log檔案,這樣即使系統發生故障,memtable中的資料沒有來得及dump到磁碟,leveldb也可以根據log檔案恢復記憶體中的memtable,不會造成系統丟失資料。這個方式就叫做 wal(write ahead log),很多傳統資料庫例如mysql也使用了wal技術來記錄日誌。
每個log檔案由多個block組成,每個block大小為32k,讀取和寫入以block為基本單位。下圖所示的log檔案包含3個block,
4 sstable檔案
memtable 是記憶體中的資料結構,儲存的內容跟硬碟上的sstable一樣,只是格式不一樣。immutable memtable的記憶體結構和memtable是完全一樣的,區別僅僅在於它是唯讀的,而memtable則是允許寫入和讀取的。當memtable寫入的資料占用記憶體到達指定大小,則自動轉換為immutable memtable,等待dump到磁碟中,系統會自動生成乙個新的memtable供寫操作寫入新資料,理解了memtable,那麼immutable memtable自然不在話下。
memtable裡的資料是按照key有序的,因此當插入新資料時,需要把這個key-value對插入到合適的位置上,以保持key有序性。memtable底層的核心資料結構是乙個跳表(skip list)。跳表是紅黑樹的一種替代資料結構,具有更高的寫入速度,而且實現起來更加簡單,請參考跳表(skip list)。
前面我們介紹了leveldb的一些記憶體資料結構和檔案,這裡開始介紹一些動態操作,例如讀取,寫入,更新和刪除資料,分層合併,錯誤恢復等操作。
6 新增、更新和刪除資料
leveldb寫入新資料時,具體分為兩個步驟:
將這個操作順序追加到log檔案末尾。儘管這是乙個磁碟操作,但是檔案的順序寫入效率還是跟高的,所以不會降低寫入的速度
如果log檔案寫入成功,那麼將這條key-value記錄插入到記憶體中memtable。
leveldb更新一條記錄時,並不會本地修改sst檔案,而是會作為一條新資料寫入memtable,隨後會寫入sst檔案,在sst檔案合併過程中,新資料會處於檔案尾部,而讀取操作是從檔案尾部倒著開始讀的,所以新值一定會最先被讀到。
leveldb刪除一條記錄時,也不會修改sst檔案,而是用乙個特殊值(墓碑值,tombstone)作為value,將這個key-value對追加到sst檔案尾部,在sst檔案合併過程中,這種值的key都會被忽略掉。
核心思想就是把寫操作轉換為順序追加,從而提高了寫的效率。
7 讀取資料
讀操作使用了如下幾個手段進行優化:
memtable + skiplist
binary search(通過 manifest 檔案)
頁快取bloom filter
週期性分層合併
ref:
leveldb原始碼 資料結構之Arena
leveldb中的記憶體管理主要是通過arena。arena管理記憶體的思想是用乙個vector來管理申請的block array of new allocated memory blocks std vectorblocks 每個block的大小為4096byte。用char 型別的alloc p...
leveldb深度剖析 儲存結構 2
關於ldb檔案說明 1 ldb檔案是按照block儲存,乙個block預設大小為4kb,當記憶體資料增長到4kb則進行寫檔案操作。2 從leveldb v1.14版本開始,資料儲存到了字尾名為ldb檔案中,不在儲存到sst檔案中。3 乙個ldb檔案包含 data block,filter block...
Loader的整體結構
loader 的整體結構。1 loader被bootsector載入到了baseofloader,偏移位址offsetofloader。純實體地址baseofloaderphyaddr。2loader的功能0 呼叫中斷,將得到的記憶體資訊,存入資料buf中。loader的功能1 載入kernel.b...