使用 LSM-Tree 思想基于.NET 6.0 C# 寫個 KV 數據庫(案例版)

文章有點長,耐心看完應該可以懂實際原理到底是啥子。

這是一個KV數據庫的C#實現,目前用.NET 6.0實現的,目前算是屬于雛形,骨架都已經完備,畢竟剛完工不到一星期。

當然,這個其實也算是NoSQL的雛形,有助于深入了解相關數據庫的內部原理概念,也有助于實際入門。

適合對數據庫原理以及實現感興趣的朋友們。

整體代碼,大概1500行,核心代碼大概500行。

為啥要實現一個數據庫

大概2018年的時候,就萌生了想自己研發一個數據庫的想法了,雖然,造輪子可能不如現有各種產品的強大,但是,能造者寥寥無幾,而且,造數據庫的書更是少的可憐,當然,不僅僅是造數據庫的書少,而是各種各樣高級的產品的創造級的書都少。

雖然,現在有各種各樣的開源,但是,像我這種底子薄的,就不能輕易的了解,這些框架的架構設計,以及相關的理念,純看代碼,沒個長時間,也不容易了解其存在的含義。

恰逢其時,前一個月看到【癡者工良】大佬的一篇《【萬字長文】使用 LSM Tree 思想實現一個 KV 數據庫 》文章給我很大觸動,讓我停滯的心,又砰砰跳了起來,雖然大佬是用GO語言實現的 ,但是,對我來講,語言還是個問題么,只要技術思想一致,我完全可以用C#實現啊,也算是對【癡者工良】大佬的致敬,我這邊緊隨其后。

當然,我自己對數據的研究也是耗時很久,畢竟,研究什么都要先從原理開始研究,從谷歌三個論文《GFS,MapReduce,BigTable》開始,但是,論文,畢竟是論文,讀不懂啊,又看了網上各種大佬的文章,還是很蒙蔽,實現的時候,也沒人交流,導致各種流產。

有時候,自己實現某個產品框架的時候,總是在想,為啥BUG都讓我處理一個遍哦,后來一想,你自己從新做個產品,也不能借鑒技術要點,那還不是從零開始,自然一一遇到BUG。

下圖就是,我在想做數據庫后,自己寫寫畫畫,但是,實際做的時候,邏輯表現總沒有那么好,當然,這個是關系型數據庫,難度比較高,下面可以看看之前的手稿,都是有想法了就畫一下。

164132ccc0d630be2ef81c905fb89ba3.pnge227585205b74310a532c56b5e807145.pngc479831f4acd11d26c7c130ffeade5b7.png

實現難度有點高,現在這個實現是KV數據庫,算是列式數據庫了,大名鼎鼎的HBase,底層數據庫引擎就是LSM-Tree的技術思想。

LSM-Tree 是啥子

LSM-Tree 英文全稱是 Log Structured Merge Tree (中文:日志結構合并樹),是一種分層,有序,面向磁盤的數據結構,其核心思想是充分了利用了,磁盤批量的順序寫要遠比隨機寫性能高的技術特點,來實現高寫入吞吐量的存儲系統的核心。

具體的說,原理就是針對硬盤,盡量追加數據,而不是隨機寫數據,追加速度要比隨機寫的速度快,這種結構適合寫多讀少的場景,所以,LSM-Tree被設計來提供比傳統的B+樹或者ISAM更好的寫操作吞吐量,通過消去隨機的本地更新操作來達到這個性能目標。

相關技術產品有Hbase、Cassandra、Leveldb、RocksDB、MongoDB、TiDB、Dynamodb、Cassandra 、Bookkeeper、SQLite 等

所以,LSM-Tree的核心就是追加數據,而不是修改數據。

LSM-Tree 架構分析

aa27fdbc191537c77f6d787b7457420d.png

其實這個圖已經表達了整體的設計思想了,主體其實就圍繞著紅色的線與黑色的線,兩部分展開的,其中紅色是寫,黑色是讀,箭頭表示數據的方向,數字表示邏輯順序。

整體包含大致三個部分,數據庫操作部分(主要為讀和寫),內存部分(緩存表和不變緩存表)以及硬盤部分(WAL Log 和 SSTable),這三個部分。

先對關鍵詞解釋一下

MemoryTable

內存表,一種臨時緩存性質的數據表,可以用 二叉排序樹實現,也可以用字典來實現,我這邊是用字典實現的。

WAL Log

WAL 英文 (Write Ahead LOG) 是一種預寫日志,用于在系統故障期間提供數據的持久性,這意味著當寫入請求到來時,數據首先添加到 WAL 文件(有時稱為日志)并刷新到更新內存數據結構之前的磁盤。

如果用過Mysql,應該就知道BinLog文件,它們是一個道理,先寫入到WAL Log里,記錄起來,然后,寫入到內存表,如果電腦突然死機了,內存里的東西肯定丟失了,那么,下一次重啟,就從WAL Log 記錄表里,從新恢復數據到當前的數據狀態。

Immutable MemoryTable

Immutable(不變的),相對于內存表來講,它是不能寫入新數據,是只讀的。

SSTable

SSTable 英文 (Sorted Strings Table) ,有序字符串表,就是有序的字符串列表,使用它的好處是可以實現稀疏索引的效果,而且,合并文件更為簡單方便,我要查某個Key,但是,它是基于 某個有序Key之間的,可以直接去文件里查,而不用都保存到內存里。

這里我是用哈希表實現的,我認為浪費一點內存是值得的,畢竟為了快,浪費點空間是值得的,所以,目前是全索引加載到內存,而數據保存在SSTable里,當然,如果是為了更好的設計,也可以自己去實現有序表來用二分查找。

我這個方便實現了之后,內存會加載大量的索引,相對來講是快的,但是,內存會大一些,空間換時間的方案。

下面開始具體的流程分析

LSM-Tree Write 路線分析

看下圖,數據寫入分析

1331e8273471f0f17fc269819a3fb716.png

跟著紅色線走,關注我從此不迷路。

LSM-Tree Write 路線分析第一步

c4cd12479c124ff72aeba0c8db8f7139.png

第一步,只有兩個部分需要注意的部分,分別是內存表和WAL.Log

寫入數據先存儲內存表,是為了快速的存儲到數據庫數據。

存儲到WAL.Log,是為了防止異常情況下數據丟失。

正常情況下,寫入到WAL.Log一份,然后,會寫入到內存一份。

當程序崩潰了,或者,電腦斷電異常了,重復服務后,就會先加載WAL.Log,按照從頭到尾的順序,恢復數據到內存表,直至結束,恢復到WAL.Log最后的狀態,也就是內存表數據最后的狀態。

這里要注意的是,當后面的不變表(Immutable MemoryTable)寫入到SSTable的時候,會清空WAL.Log文件,并同時把內存表的數據直接寫入到WAL.log表中。

LSM-Tree Write 路線分析第二步

ef030d451698c77aab46727c5ea00969.png

第二步,比較簡單,就是在內存表count大于一定數的時候,就新增一個內存表的同時, 把它變為 Immutable MemoryTable (不變表),等待SSTable的落盤操作,這個時候,Immutable MemoryTable會有多個表存在。

LSM-Tree Write 路線分析第三步

9562a4bded588cb506b414a5de27f91a.png

第三步,就是數據庫會定時檢查 Immutable MemoryTable (不變表)不變表是否存在,如果存在,就會直接落盤為SSTable表,不論當前內存里有多少 Immutable MemoryTable (不變表)。

默認從內存落盤的第一級SSTable都是 Level 0,然后,內置了當前的時間,所以是兩級排序,先分級別,然后,分時間。

LSM-Tree Write 路線分析第四步

13104f9db980b8261666120581251d7a.png

第四步,其實就是段合并或者級合并壓縮,就是判斷 level0 這一個級別的所有 SSTable文件(SSTable0,SSTable1,SSTable2),判斷它們的總大小或者判斷它們的總個數來判斷,它們需不需要進行合并。

其中 Level 0 的大小如果是10M,那么 ,Level 1的大小就是 100M,依此類推。

當Level0的所有SSTable文件超過了10M,或者限定的大小,就會從按照WAL.Log的順序思路,重新合并為一個大文件,先老數據再新數據這樣遍歷合并,如果已經刪除的,則直接剔除在外,只保留最新狀態。

如果 Level1的(全部SSTable)大小 超過100M,那么,觸發Level1的收縮動作,執行過程跟Level0一樣的操作,只是級別不同。

這樣壓縮的好處是使數據盡可能讓文件量盡可能的少,畢竟,文件多,管理就不是很方便。

至此,寫入路線已經分析完畢

查詢的時候,要先新數據,后舊數據,而分段合并壓縮的時候,要先老數據墊底,新數據刷狀態,這個是實現的時候需要注意的點。

LSM-Tree Read 路線分析

d1a4cdc7763cff757ec049bc8bbf42ba.png

這就是數據的查找過程,跟著黑線和數字標記,很容易就看到了其訪問順序

  1. 1.?MemoryTable (內存表)

  2. 2.?Immutable MemoryTable (不變表)

  3. 3.?Level 0-N (SSTableN-SSTable1-SSTable0) (有序字符串表)

基本上來說就這三部分,而級別表是從0級開始往下找的,而每級內部的SSTable是從新到舊開始找的,找到就返回,不論key是刪除還是正常的狀態。

LSM-Tree 架構分析與實現

e56c8521ec7d71c2a246bdea02330c69.png

核心思想:

其實就是一個時間有序的記錄表,會記錄每個操作,相當于是一個消息隊列,記錄一系列的動作,然后,回放動作,就獲取到了最新的數據狀態,也類似CQRS中的Event Store(事件存儲),概念是相同的,那么實現的時候,就明白是一個什么本質。

Wal.log和SSTable,都是為了保證數據能落地持久化不丟失,而MemoryTable,偏向臨時緩存的概念,當然,也有為了加速訪問的作用。

所以,從這幾個點來看,就分為了以下幾個大的對象

  1. 1.?Database 數據庫( 起到對Wal.log,SSTable和MemoryTable 的管理職責)

  2. 2.?Wal.log(記錄臨時數據日志)

  3. 3.?MemoryTable(記錄數據到內存,同時為數據庫查找功能提供接口服務)

  4. 4.?SSTable(管理SSTable文件,并提供SSTable的查詢功能)

所以,針對這幾個對象來設計相關的類接口設計。

KeyValue (具體數據的結構)

設計的時候,要先設計實際數據的結構,我是這樣設計的

主要有三個主要的信息,key, DataValue,Deleted ,其中DataValue是Byte[]類型的,我這邊寫入到文件里的話,是直接寫入的。

///?<summary>
///?數據信息?kv
///?</summary>
public?class?KeyValue
{public?string?Key?{?get;?set;?}public?byte[]?DataValue?{?get;?set;?}public?bool?Deleted?{?get;?set;?}private?object?Value;public?KeyValue()?{?}public?KeyValue(string?key,?object?value,?bool?Deleted?=?false){Key?=?key;Value?=?value;DataValue?=?value.AsBytes();this.Deleted?=?Deleted;}public?KeyValue(string?key,?byte[]?dataValue,?bool?deleted){Key?=?key;DataValue?=?dataValue;Deleted?=?deleted;}///?<summary>///?是否存在有效數據,非刪除狀態///?</summary>///?<returns></returns>public?bool?IsSuccess(){return?!Deleted?||?DataValue?!=?null;}///?<summary>///?值存不存在,無論刪除還是不刪除///?</summary>///?<returns></returns>public?bool?IsExist(){if?(DataValue?!=?null?&&?!Deleted?||?DataValue?==?null?&&?Deleted){return?true;}return?false;}public?T?Get<T>()?where?T?:?class{if?(Value?==?null){Value?=?DataValue.AsObject<T>();}return?(T)Value;}public?static?KeyValue?Null?=?new?KeyValue()?{?DataValue?=?null?};
}

IDataBase (數據庫接口)

主要對外交互用的主體類,數據庫類,增刪改查接口,都用 get,set,delete 表現。

///?<summary>
///?數據庫接口
///?</summary>
public?interface?IDataBase?:?IDisposable
{///?<summary>///?數據庫配置///?</summary>IDataBaseConfig?DataBaseConfig?{?get;?}///?<summary>///?獲取數據///?</summary>KeyValue?Get(string?key);///?<summary>///?保存數據(或者更新數據)///?</summary>bool?Set(KeyValue?keyValue);///?<summary>///?保存數據(或者更新數據)///?</summary>bool?Set(string?key,?object?value);///?<summary>///?獲取全部key///?</summary>List<string>?GetKeys();///?<summary>///?刪除指定數據,并返回存在的數據///?</summary>KeyValue?DeleteAndGet(string?key);///?<summary>///?刪除數據///?</summary>void?Delete(string?key);///?<summary>///?定時檢查///?</summary>void?Check(object?state);///?<summary>///?清除數據庫所有數據///?</summary>void?Clear();
}

IDataBase.Check (定期檢查)

這個是定期檢查Immutable MemoryTable(不變表)的定時操作,主要依賴IDataBaseConfig.CheckInterval 參數配置其觸發間隔。

它的職責是檢查內存表和檢查SSTable 是否觸發分段合并壓縮的操作。

public?void?Check(object?state)
{//Log.Info($"定時心跳檢查!");if?(IsProcess){return;}if?(ClearState){return;}try{Stopwatch?stopwatch?=?Stopwatch.StartNew();IsProcess?=?true;checkMemory();TableManage.Check();stopwatch.Stop();GC.Collect();Log.Info($"定時心跳處理耗時:{stopwatch.ElapsedMilliseconds}毫秒");}finally{IsProcess?=?false;}
}

IDataBaseConfig (數據庫配置文件)

數據庫的配置文件,數據庫保存在哪里,以及生成SSTable時的閾值配置,還有檢測間隔時間配置。

///?<summary>
///?數據庫相關配置
///?</summary>
public?interface?IDataBaseConfig
{///?<summary>///?數據庫數據目錄///?</summary>public?string?DataDir?{?get;?set;?}///?<summary>///?0?層的?所有?SsTable?文件大小總和的最大值,單位?MB,超過此值,該層?SsTable?將會被壓縮到下一層///?每層數據大小是上層的N倍///?</summary>public?int?Level0Size?{?get;?set;?}///?<summary>///?層與層之間的倍數///?</summary>public?int?LevelMultiple?{?get;?set;?}///?<summary>///?每層數量閾值///?</summary>public?int?LevelCount?{?get;?set;?}///?<summary>///?內存表的?kv?最大數量,超出這個閾值,內存表將會被保存到?SsTable?中///?</summary>public?int?MemoryTableCount?{?get;?set;?}///?<summary>///?壓縮內存、文件的時間間隔,多久進行一次檢查工作///?</summary>public?int?CheckInterval?{?get;?set;?}
}

IMemoryTable (內存表)

1c7c001bea483481cdc0059942c49835.png

這個表其實算是對內存數據的管理表了,主要是管理 MemoryTableValue 對象,這個對象是通過哈希字典來實現的,當然,你也可以選擇其他結構,比如有序二叉樹等。

///?<summary>
///?內存表(排序樹,二叉樹)
///?</summary>
public?interface?IMemoryTable?:?IDisposable
{IDataBaseConfig?DataBaseConfig?{?get;?}///?<summary>///?獲取總數///?</summary>int?GetCount();///?<summary>///?搜索(從新到舊,從大到小)///?</summary>KeyValue?Search(string?key);///?<summary>///?設置新值///?</summary>void?Set(KeyValue?keyValue);///?<summary>///?刪除key///?</summary>void?Delete(KeyValue?keyValue);///?<summary>///?獲取所有?key?數據列表///?</summary>///?<returns></returns>IList<string>?GetKeys();///?<summary>///?獲取所有數據///?</summary>///?<returns></returns>(List<KeyValue>?keyValues,?List<long>?times)?GetKeyValues(bool?Immutable);///?<summary>///?獲取不變表的數量///?</summary>///?<returns></returns>int?GetImmutableTableCount();///?<summary>///?開始交換///?</summary>void?Swap(List<long>?times);///?<summary>///?清空全部數據///?</summary>void?Clear();
}

MemoryTableValue (對象的實現)

主要是通過 Immutable 這個屬性實現了對不可變內存表的標記,具體實現是通過判斷 IDataBaseConfig.MemoryTableCount (內存表的 kv 最大數量)來實現標記的。

public?class?MemoryTableValue?:?IDisposable
{public?long?Time?{?get;?set;?}?=?IDHelper.MarkID();///?<summary>///?是否是不可變///?</summary>public?bool?Immutable?{?get;?set;?}?=?false;///?<summary>///?數據///?</summary>public?Dictionary<string,?KeyValue>?Dic?{?get;?set;?}?=?new();public?void?Dispose(){Dic.Clear();}public?override?string?ToString(){return?$"Time?{Time}?Immutable:{Immutable}";}
}

什么時機表狀態轉換為 Immutable MemoryTable(不變表)的

我這里實現的是從Set的入口處實現的,如果數目大于IDataBaseConfig.MemoryTableCount (內存表的 kv 最大數量)就改變其狀態

public?void?Check()
{if?(CurrentMemoryTable.Dic.Count()?>=?DataBaseConfig.MemoryTableCount){var?value?=?new?MemoryTableValue();dics.Add(value.Time,?value);CurrentMemoryTable.Immutable?=?true;}
}

IWalLog

wallog,就簡單許多,就直接把KeyValue 寫入到文件即可,為了保證WalLog的持續寫,所以,對象內部保留了此文件的句柄。而SSTable,就沒有必要了,隨時讀。

///?<summary>
///?日志
///?</summary>
public?interface?IWalLog?:?IDisposable
{///?<summary>///?數據庫配置///?</summary>IDataBaseConfig?DataBaseConfig?{?get;?}///?<summary>///?加載Wal日志到內存表///?</summary>///?<returns></returns>IMemoryTable?LoadToMemory();///?<summary>///?寫日志///?</summary>void?Write(KeyValue?data);///?<summary>///?寫日志///?</summary>void?Write(List<KeyValue>?data);///?<summary>///?重置日志文件///?</summary>void?Reset();
}

ITableManage (SSTable表的管理)

為了更好的管理SSTable,需要有一個管理層,這個接口就是它的管理層,其中SSTable會有多層,每次用 Level+時間戳+db 作為文件名,用作外部識別。

///?<summary>
///?表管理項
///?</summary>
public?interface?ITableManage?:?IDisposable
{IDataBaseConfig?DataBaseConfig?{?get;?}///?<summary>///?搜索(從新到老,從大到小)///?</summary>KeyValue?Search(string?key);///?<summary>///?獲取全部key///?</summary>List<string>?GetKeys();///?<summary>///?檢查數據庫文件,如果文件無效數據太多,就會觸發整合文件///?</summary>void?Check();///?<summary>///?創建一個新Table///?</summary>void?CreateNewTable(List<KeyValue>?values,?int?Level?=?0);///?<summary>///?清理某個級別的數據///?</summary>///?<param?name="Level"></param>public?void?Remove(int?Level);///?<summary>///?清除數據///?</summary>public?void?Clear();
}

ISSTable(SSTable 文件)

SSTable的內容管理,應該就是LSM-Tree的核心了,數據的合并,以及數據的查詢,寫入,加載,都是偏底層的操作,需要一丟丟的數據庫知識。

///?<summary>
///?文件信息表?(存儲在IO中)
///?元數據?|?索引列表?|?數據區(數據修改只會新增,并修改索引列表數據)?
///?</summary>
public?interface?ISSTable?:?IDisposable
{///?<summary>///?數據地址///?</summary>public?string?TableFilePath();///?<summary>///?重寫文件///?</summary>public?void?Write(List<KeyValue>?values,?int?Level?=?0);///?<summary>///?數據位置///?</summary>public?Dictionary<string,?DataPosition>?DataPositions?{?get;?}///?<summary>///?獲取總數///?</summary>///?<returns></returns>public?int?Count?{?get;?}///?<summary>///?元數據///?</summary>public?ITableMetaInfo?FileTableMetaInfo?{?get;?}///?<summary>///?查詢數據///?</summary>///?<param?name="key"></param>///?<returns></returns>public?KeyValue?Search(string?key);///?<summary>///?有序的key列表///?</summary>///?<returns></returns>public?List<string>?SortIndexs();///?<summary>///?獲取位置///?</summary>DataPosition?GetDataPosition(string?key);///?<summary>///?讀取某個位置的值///?</summary>public?object?ReadValue(DataPosition?position);///?<summary>///?加載所有數據///?</summary>///?<returns></returns>public?List<KeyValue>?ReadAll(bool?incloudDeleted?=?true);///?<summary>///?獲取所有keys///?</summary>///?<returns></returns>public?List<string>?GetKeys();///?<summary>///?獲取表名///?</summary>///?<returns></returns>public?long?FileTableName();///?<summary>///?文件的大小///?</summary>///?<returns></returns>public?long?FileBytes?{?get;?}///?<summary>///?獲取級別///?</summary>public?int?GetLevel();
}

IDataPosition(數據稀疏索引算是)

方便數據查詢方便和方便從SSTable里讀取到實際的數據內容。

///?<summary>
///?數據的位置
///?</summary>
public?interface?IDataPosition
{///?<summary>///?索引起始位置///?</summary>public?long?IndexStart?{?get;?set;?}///?<summary>///?開始地址///?</summary>public?long?Start?{?get;?set;?}///?<summary>///?數據長度///?</summary>public?long?Length?{?get;?set;?}///?<summary>///?key的長度///?</summary>public?long?KeyLength?{?get;?set;?}///?<summary>///?是否已經刪除///?</summary>public?bool?Deleted?{?get;?set;?}public?byte[]?GetBytes();
}

數據結構分析

內部表的結構就不用說了,很簡單,就是一個哈希字典,而有兩個結構是要具體分析的,那就是 WALLog和SSTable文件。

WALLog 結構分析

093976ad0f81bfd50f3bf405649240e8.png

這個圖橫向不好畫,我畫成豎向了,WalLog里面存儲的就是時間序的KeyValue數據,當它加載到Memory Table的時候,其實就是按照我所標的數字順序依次疊加到最后的狀態的。

同理,SSTable 數據分段合并壓縮的時候,其實是跟這個一個原理的。

SSTable 結構分析

700eb8dab03445f4a35dcbf963c8ea8d.png

SSTable,它本身是一個文件 名字大致如下:

0_16586442986880000.db

格式為 層級_時間戳.db 這樣的方式搞的命名規則,為此我還搞了一個生成時間序不重復 ID的簡單算法。

SSTable 數據區

49b334baeb16b8573b330d48eeed21b3.png

數據區就很簡單,把KeyValue.DataValue直接ToJson 就可以了,然后,直接寫文件。

SSTable 稀疏索引區

c9d7657138ecd978a90a3ba0a58dee52.png

這個區是按照與數據區對應的key的順序寫入的,主要是把DataValue對應的開始地址和結束地址放入到這個數據區了,另外把key也寫入進去了。

好處是為了,當此SSTable加載索引(IDataPosition)到內存,省的把數據區的內容也加載進去,查找就方便許多,這也是索引的作用。

元數據區

771a20464b259093cbe8353703200a76.png

這個按照協議來講,屬于協議頭,但是為啥放最后面呢,其實是為了計算方便,這也算是一個小妙招。

其中不僅包含了數據區的開始和結束,稀疏索引區的開始和結束,還包含了,此SSTable的版本和創建時間,以及當前SSTable所在的級別。

SSTable 分段合并壓縮

剛看這段功能邏輯的時候,腦子是懵的,使勁看了好久,分析了好久,還是把它寫出來了,剛開始不理解,后來理解了,寫著就容易許多了。

看下圖:

c1853505ee240d269ebf58ccb49d42d1.png

其實合并是有狀態的,這個就是中間態,我把他放到了圖中間,然后,用白色的虛框表示。

整體邏輯就是,先從內存中定時把不變表生成為0級的SSTable,然后,0級就會有許多文件,如果這些文件大小超過了閾值,就合并此級的文件為一個大文件,按照WalLog的合并原理,然后把信息重新寫入到本地為1級SSTable即可。

以此類推。

下面一個動圖說明其合并效果。

73cdfb8b6662a2d50337b44cf8cd0573.gif

這個動圖也說明一些事情,有此圖,估計對原理就會多懂一些。

LSMDatabase 性能測試

目前我這邊測試用例都挺簡單,如果有bug,就直接改了。我這邊測試是,直接寫入一百萬條數據,測試結果如下:

keyvalue 數據長度:151 實際文件大小:217 MB 插入1000000條數據 耗時:79320毫秒 或79.3207623秒,平均每秒插入:52631條

keyvalue 數據長度:151 實際文件大小:221 MB 插入1000000條數據 耗時:27561毫秒 或 27.5616519 秒,平均每秒插入:37037條

  1. 1.?keyvalue 數據長度:176

  2. 2.?實際文件大小:215 MB

  3. 3.?插入1000000條數據 耗時:29545毫秒 或 29.5457999 秒,

  4. 4.?平均每秒插入:34482條 或 30373 等( 配置不一樣,環境不一樣,會有不同,但是大致差不多)

  5. 5.?多次插入數據長度不同,配置不同,插入速度都會受到影響

加載215 MB 1000000條數據條數據 耗時:2322 毫秒,也就是2秒(加載SSTable)

內存穩定后占用500MB左右。

穩定查詢耗時: 百條查詢平均每條查詢耗時: 0毫秒。可能是因為用了字典的緣故,查詢速度會快點,但是,特別點查詢會有0.300左右的耗時個別現象。

查詢keys,一百萬條耗時3秒,這個有點耗時,應該是數據量太大了。

13d08f5f5b4a6f6d943972f789932075.pngc090d7d81ab62bc3ff4619ef575c1553.png26d59de677f41fd65bc23289727ede95.pngddb67e6420729ca2a20d4c9e7ffec862.png

至此,此項目已經結束,雖然,還沒有經歷過壓力測試,但是,整體骨架和內容已經完備,可以根據具體情況修復完善。目前我這邊是沒啥子問題的。

總結

任何事情的開始都是艱難的,跨越時間的長河,一步一步的學習,才有了今天它的誕生,會了就是會了,那么,應對下一個相關問題就會容易許多,我對這樣的壁壘稱之為,知識的屏障。

一葉障目,還真是存在,如何突破,唯有好奇心,堅持下去,一點點挖掘。

參考資料

【萬字長文】使用 LSM Tree 思想實現一個 KV 數據庫

https://www.cnblogs.com/whuanle/p/16297025.html

肖漢松:《從0開始:500行代碼實現 LSM 數據庫》

https://mp.weixin.qq.com/s/kCpV0evSuISET7wGyB9Efg

cstack : 讓我們建立一個簡單的數據庫

https://cstack.github.io/db_tutorial/

數據庫內核雜談 - 一小時實現一個基本功能的數據庫

https://www.jianshu.com/p/76e5cb53c864

谷歌三大論文 GFS,MapReduce,BigTable 中的GFS和BigTable

致謝名單

  1. 1.?癡者工良

  2. 2.?陶德

雖然與以上大佬沒有太過深入的交流,畢竟咖位還是有點高的,但是,通過文章以及簡單的交流中,讓我對數據庫的研究更深一步,甚至真實的搞出來了,再次感謝。

代碼地址

https://github.com/kesshei/LSMDatabaseDemo.git

https://gitee.com/kesshei/LSMDatabaseDemo.git

一鍵三連呦!,感謝大佬的支持,您的支持就是我的動力!

版權

藍創精英團隊(公眾號同名,CSDN同名)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/284400.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/284400.shtml
英文地址,請注明出處:http://en.pswp.cn/news/284400.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

35.使用攔截器實現權限驗證

轉自&#xff1a;https://wenku.baidu.com/view/84fa86ae360cba1aa911da02.html 為了說明此問題&#xff0c;我們建立struts2auth項目&#xff0c;流程圖如下&#xff1a; 簡短說明&#xff1a;當我們訪問main.jsp頁面&#xff0c;并試圖通過此頁面中的鏈接地址&#xff1a;not…

如何保證緩存和數據庫的一致性?

1. 問題分析 2. Cache-Aside 2.1 讀緩存 2.2 寫緩存 2.3 延遲雙刪 2.4 如何確保原子性 3. Read-Through/Write-Through 3.1 Read-Through 3.2 Write-Through 4. Write Behind 很多小伙伴在面試的時候&#xff0c;應該都遇到過類似的問題&#xff0c;如何確保緩存和數據庫…

Pressed狀態和clickable,duplicateParentState的關系

做Android開發的人都用過Selector,可以方便的實現View在不同狀態下的背景。不過&#xff0c;相信大部分開發者遇到過和我一樣的問題&#xff0c;本文會從源碼角度&#xff0c;解釋這些問題。 首先&#xff0c;這里簡單描述一下&#xff0c;我遇到的問題&#xff1a; 界面上有個…

Hbase筆記4 java操作Hbase

暫無轉載于:https://www.cnblogs.com/mrxiaohe/p/6512481.html

【招聘(南京)】 慧咨環球南京研發中心 .NET和Blazor 前端

主要的亮點快速增長的、產品導向型的全球性科技公司設計和開發市場領先的軟件解決方案WLB — 工作生活相平衡澳洲排名前五的軟件公司混合辦公 — 3天在家辦公&#xff0c;2天在辦公室辦公在C#和.NET開發&#xff0c;企業級系統研發&#xff0c;軟件工程方面有長期的優秀實踐和技…

用Python+Django在Eclipse環境下開發web網站【轉】

一、創建一個項目如果這是你第一次使用Django&#xff0c;那么你必須進行一些初始設置。也就是通過自動生成代碼來建立一個Django項目--一個Django項目的設置集&#xff0c;包含了數據庫配置、Django詳細選項設置和應用 特性配置&#xff0c;具體操作步驟如下所示。 1.新建Djan…

[轉]數據結構KMP算法配圖詳解(超詳細)

KMP算法配圖詳解 前言 KMP算法是我們數據結構串中最難也是最重要的算法。難是因為KMP算法的代碼很優美簡潔干練&#xff0c;但里面包含著非常深的思維。真正理解代碼的人可以說對KMP算法的了解已經相當深入了。而且這個算法的不少東西的確不容易講懂&#xff0c;很多正規的書本…

BGP-MED-2

BGP-MED-2如圖&#xff1a;當AS100去往AS300的60、10的網絡時&#xff0c;60走R3&#xff0c;10走R1!使用MED屬性影響選路&#xff01; R2的配置 bgp 200peer 1.1.1.1 as-number 100 peer 1.1.1.1 ebgp-max-hop 255 peer 1.1.1.1 connect-interface LoopBack0peer 4.4.4.4 as-n…

WPF 實現 Gitee 氣泡菜單(一)

WPF 實現 Gitee 氣泡菜單&#xff08;一&#xff09;氣泡菜單&#xff08;一&#xff09;作者&#xff1a;WPFDevelopersOrg原文鏈接&#xff1a; https://github.com/WPFDevelopersOrg/WPFDevelopers框架使用大于等于.NET40&#xff1b;Visual Studio 2022;項目使用 MIT 開…

[轉]LVS負載均衡(LVS簡介、三種工作模式、十種調度算法)

一、LVS簡介 LVS&#xff08;Linux Virtual Server&#xff09;即Linux虛擬服務器&#xff0c;是由章文嵩博士主導的開源負載均衡項目&#xff0c;目前LVS已經被集成到Linux內核模塊中。該項目在Linux內核中實現了基于IP的數據請求負載均衡調度方案&#xff0c;其體系結構如圖1…

一張圖看懂微軟Power BI系列組件

一、Power BI簡介 Power BI是微軟最新的商業智能&#xff08;BI&#xff09;概念&#xff0c;它包含了一系列的組件和工具。話不多說&#xff0c;直接上圖吧&#xff1a; Power BI的核心理念就是讓我們用戶不需要強大的技術背景&#xff0c;只需要掌握Excel這樣簡單的工具就能快…

互聯網項目總結

2019獨角獸企業重金招聘Python工程師標準>>> 從去年年底開始專門被分配到互聯網小組做項目&#xff0c;一直想做個總結&#xff0c;但是苦于太貪玩。好吧&#xff0c;借著小組技術交流來一發。這里只對自己新學習的技術或者一些小技巧做簡要概述&#xff0c;不做深究…

【ArcGIS微課1000例】0036:分式標注案例教程

【拓展閱讀】:【ArcGIS Pro微課1000例】0015:ArcGIS Pro中屬性字段分式標注案例教程 文章目錄 1. 符號化2. 分式標注1. 符號化 右鍵數據圖層→符號系統,打開符號系統對話框,住符號系統選擇【唯一值】,字段1選擇NAME。 唯一值標注效果: 2. 分式標注 雙擊打開圖層屬性,切…

【轉】 ConstraintLayout 完全解析 快來優化你的布局吧

轉自&#xff1a; http://blog.csdn.net/lmj623565791/article/details/78011599 本文出自張鴻洋的博客 一、概述 ConstraintLayout出現有一段時間了&#xff0c;不過一直沒有特別去關注&#xff0c;也多多少少看了一些文字介紹&#xff0c;多數都是對使用可視化布局拖拽&#…

IoTDB 的C# 客戶端發布 0.13.0.7

IoTDB C# Client 0.13.0.7 已經發布&#xff0c; 此版本更新的內容為筆者為Apache-IoTDB-Client-CSharp實現了Ado.Net的兼容層&#xff0c;降低了對IoTDB的使用門檻。于此同時&#xff0c; IoTSharp也開始支持了IoTDB的數據入庫&#xff0c;隨著晚些時候IoTSharp 2.7 版本的發布…

[轉]Docker超詳細基礎教程,快速入門docker

一、docker概述 1.什么是docker Docker 是一個開源的應用容器引擎&#xff0c;基于 Go 語言 并遵從 Apache2.0 協議開源。 Docker 可以讓開發者打包他們的應用以及依賴包到一個輕量級、可移植的容器中&#xff0c;然后發布到任何流行的 Linux 機器上&#xff0c;也可以實現虛擬…

【Zookeeper】源碼分析之服務器(一)

一、前言 前面已經介紹了Zookeeper中Leader選舉的具體流程&#xff0c;接著來學習Zookeeper中的各種服務器。 二、總體框架圖 對于服務器&#xff0c;其框架圖如下圖所示 說明&#xff1a; ZooKeeperServer&#xff0c;為所有服務器的父類&#xff0c;其請求處理鏈為PrepReques…

linux下配置samba服務器(以CentOS6.7為例)

一、簡介&#xff08;百度百科&#xff09;Samba是在Linux和UNIX系統上實現SMB協議的一個免費軟件&#xff0c;由服務器及客戶端程序構成。SMB&#xff08;Server Messages Block&#xff0c;信息服務塊&#xff09;是一種在局域網上共享文件和打印機的一種通信協議&#xff0c…

【ArcGIS微課1000例】0037:上下標標注記案例教程

在利用ArcGIS進行制圖時&#xff0c;進行標注(Label) 或注記(Annolation) 是必不可少的。但是除了常規的標注和注記以外&#xff0c;還時常需要一些特殊的標注或注記&#xff0c;比如上標、下標等。 文章目錄一、上標標注方法二、下標標注方法一、上標標注方法 上下標代碼模板…

Redis——緩存擊穿、穿透、雪崩

1、緩存穿透&#xff1a; &#xff08;1&#xff09;問題描述&#xff1a;key對應的數據并不存在&#xff0c;每次請求訪問key時&#xff0c;緩存中查找不到&#xff0c;請求都會直接訪問到數據庫中去&#xff0c;請求量超出數據庫時&#xff0c;便會導致數據庫崩潰。如一個用…