基于跳表实现的键值型存储引擎
¶1 项目说明
Key-Value 存储引擎 🌻
基于 跳表 实现的 KV 存储引擎,使用 C++ 实现。在随机读写情况下,该项目每秒可处理请求数(QPS):24.39 W,每秒可处理读请求数:18.41 W。(项目作者说明,但笔者测试不相同)
提供接口 :
🔹 insert_element 插入数据
🔸 delete_element 删除数据
🔹 search_element 查询数据
🔸 update_element 更新数据 (笔者添加)
🔹 display_list 打印跳跃表
🔸 dump_file 数据落盘
🔹 load_file 加载数据
🔸 size 返回数据规模
🔹 clear 清空跳表 (笔者添加)
项目地址:Skiplist-CPP: A tiny KV storage based on skiplist written in C++ language
补充说明 :该项目作者:程序员 Carl,公众号:代码随想录。笔者对其代码进行学习,并尝试修改。修改的内容见下文。
¶2 项目代码
所有实现代码位于 skiplist.h
头文件中,CPP 文件中 include 它就可以使用。内部关键在于定义了 SkipList
结构,以及 Node
结构。
¶2.1 头文件
1 |
¶2.2 基本定义
1 |
|
说明 🌞
🔹 宏定义数据落盘位置,以及加载数据位置;
🔸 定义互斥量锁,用于写操作中的临界区;
🔹 定义分隔符,用于加载数据是识别 Key 和 Value 。
¶2.3 Node
¶2.3.1 Node 定义
Node
结构是 SkipList
实现的基础。其定义如下:
1 | template<typename K, typename V> |
说明 🌞
🔹 私有数据成员 key , value ,公有数据成员 forward ,node_level
🔸 成员函数:构造函数,析构函数,键值对操作函数
注意 🙋♂
forward 是一个指针数组,其元素是指向 Node 的指针。
¶2.3.2 构造函数和析构函数
1 | template<typename K, typename V> |
¶2.3.3 键值对操作函数
1 | template<typename K, typename V> |
¶2.4 SkipList
¶2.4.1 SkipList 定义
1 | template <typename K, typename V> |
说明 🌞
🔹 私有数据成员 _max_level, _skip_list_level, _element_count, header, _file_writer, file_reader
🔸 成员函数:构造函数,析构函数,创建节点函数,增删改查操作函数,清空跳表,数据落盘和加载数据及其相关函数,打印跳表函数,返回跳表节点数函数,获得随机层高函数 。
¶2.4.2 构造函数、析构函数、节点构造函数
1 | template<typename K, typename V> |
¶2.4.3 获得随机层高函数以及返回跳表大小函数
1 | template<typename K, typename V> |
注意 🙋 层高的选择一般根据 幂次定律 (power law),越大的数出现的概率越小 。
¶2.4.4 insert_element()
1 | template<typename K, typename V> |
说明 🌞 :代码详细解释见注释。
注意 🙋♂
🔸 update 数组存在于所有 写 操作中,起至关重要的作用。update[i] 记录第 i 层最后符合要求的节点
🔹 所有 写 操作(增删改)都需要判断,1️⃣ 当前 key 存在,2️⃣ 当前 key 不存在,这两种情况。
¶2.4.5 delete_element()
1 | template<typename K, typename V> |
说明 🌞 :代码详细解释见注释。
¶2.4.6 update_element()
1 | // 笔者添加了修改值的操作 |
说明 🌞 :代码详细解释见注释。
注意 🙋♂ :笔者添加了修改值的操作,并提供了两种类型的操作
🔹 硬更新:如果键不存在,直接打印提示信息 flag = false
🔸 软更新:如果键不存在,允许创建它 flag = true
¶2.4.7 search_element()
1 | template<typename K, typename V> |
说明 🌞 :代码详细解释见注释。
¶2.4.8 打印跳表
1 | template<typename K, typename V> |
¶2.4.9 数据落盘、数据加载函数及其辅助函数
1 | // Dump data in memory to file |
说明 🌞 :代码详细解释见注释。
¶2.4.10 clear()
1 | template<typename K, typename V> |
说明 🌞 :代码详细解释见注释。
注意 🙋♂ :清空除了头节点以外的节点并且头节点 forward 初始化;
¶3 项目测试
¶3.1 skiplist.h
测试
针对所有提供的 API 进行测试:
1 |
|
测试结果 ⭐️
🔹 插入操作:
🔸 查找操作:
🔹 更新操作:
🔸 删除操作:
🔹 数据落盘操作:
🔸 打印跳表操作:
🔹 清空跳表操作:
🔸 数据加载操作:
🔹 加载后的跳表:
注意 🙋♂ :
1️⃣ 数据落盘只是存储了键值对;
2️⃣ 数据加载是按照文件中的键值对重新进行 insert
操作;
3️⃣ 数据加载后的跳表节点数和内容相同,但是层数和之间的关系可能变化。
¶3.2 增删改查 QPS 测试
对增删改查进行测试。测试方法:设定固定的操作数,设置层高为 18,采用随机插入数据,看各个操作运行时间。
具体代码见:stress-test/stress_test.cpp
运行设备 :阿里云服务器 ECS 共享型 n4 (1核2G, 1 M 带宽)
运行结果 :
结果统计 :
具体操作\数据规模 | 10 w | 50 w | 100 w |
---|---|---|---|
插入 | 0.099330 s | 0.840242 s | 2.09992 s |
删除 | 0.084905 s | 0.777721 s | 1.97519 s |
修改 | 0.106549 s | 0.971254 s | 2.42625 s |
查看 | 0.089328 s | 0.862964 s | 2.15155 s |
以 100 w 数据结果计算 QPS:
🔹 插入 :47.62 w 🔸 删除 :50. 63 w 🔹 修改 :41.22 w 🔸 查看 :46.48 w
😲 不知为什么,比项目 README 文档中测试的结果要快一倍。