lsmTree
LSM-Tree 简介
LSM-Tree(Log-Structured Merge-Tree)是一种为高写入吞吐设计的存储结构,广泛应用于 NoSQL 数据库(如 LevelDB、RocksDB)。其核心思想是将随机写入转换为顺序写入,并通过分层合并优化数据管理。
LSM-Tree 的核心组件
-
MemTable
- 内存中的可变数据结构(通常为跳表或平衡树),用于快速处理写入操作。
- 写入数据时,首先插入 MemTable。
- 当 MemTable 达到阈值时,转为不可变的 SSTable 并写入磁盘。
-
SSTable(Sorted String Table)
- 磁盘上的不可变有序文件,按层级组织。
- 每层包含多个 SSTable,高层级的数据量是低层级的倍数(如 10 倍)。
-
Compaction(合并)
- 当某层 SSTable 数量超过阈值时,触发合并操作。
- 合并相邻层的 SSTable,去重并删除无效数据,生成新的 SSTable 到下一层。
LSM-Tree 操作流程
- 写入:数据先写入 MemTable。
- 读取:先查 MemTable,再逐层查询 SSTable。
- 删除:插入一个删除标记(Tombstone),合并时清理数据。
- 合并:后台线程将小 SSTable 合并为大 SSTable,减少查询时的 I/O 开销。
C++ 实现简易 LSM-Tree
1. 内存表(MemTable)实现
使用 std::map
模拟内存中的有序结构。
#include map>
#include string>
#include vector>
#include fstream>
#include sstream>
class MemTable {
private:
std::map<std::string, std::string> data;
size_t max_size;
bool tombstone = false;
public:
MemTable(size_t max_size = 1024) : max_size(max_size) {}
// 插入或更新键值对
void put(const std::string& key, const std::string& value) {
data[key] = value;
if (data.size() >= max_size) {
tombstone = true; // 标记为可转储
}
}
// 标记删除
void remove(const std::string& key) {
data[key] = "TOMBSTONE";
}
// 查找键值
std::string get(const std::string& key) {
auto it = data.find(key);
if (it != data.end()) {
return it->second;
}
return "";
}
// 判断是否需要转储到磁盘
bool need_flush() const {
return tombstone;
}
// 获取所有键值对(按序)
std::vector<std::pair<std::string, std::string>> export_data() {
return std::vector<std::pair<std::string, std::string>>(data.begin(), data.end());
}
// 清空内存表
void clear() {
data.clear();
tombstone = false;
}
};
2. SSTable 实现
将内存表数据写入有序文件,支持从文件加载数据。
class SSTable {
private:
std::string filename;
public:
SSTable(const std::string& filename) : filename(filename) {}
// 将内存表数据写入 SSTable 文件
void write(const std::vector<std::pair<std::string, std::string>>& data) {
std::ofstream file(filename, std::ios::binary);
for (const auto& entry : data) {
file < entry.first < "," < entry.second < "\n";
}
file.close();
}
// 从 SSTable 文件中查找键值
std::string read(const std::string& key) {
std::ifstream file(filename, std::ios::binary);
std::string line;
while (std::getline(file, line)) {
std::istringstream iss(line);
std::string k, v;
std::getline(iss, k, ',');
std::getline(iss, v);
if (k == key) {
return v;
}
}
return "";
}
};
3. LSM-Tree 主逻辑
管理内存表和 SSTable 的层级合并。
#include vector>
#include filesystem>
namespace fs = std::filesystem;
class LSMTree {
private:
MemTable memtable;
std::vector<std::vector<SSTable>> levels;
size_t level0_max = 4; // Level 0 最多容纳 4 个 SSTable
public:
LSMTree() {
levels.resize(2); // 简化实现:仅 Level 0 和 Level 1
}
// 插入数据
void put(const std::string& key, const std::string& value) {
memtable.put(key, value);
if (memtable.need_flush()) {
flush_memtable();
}
}
// 删除数据
void remove(const std::string& key) {
memtable.remove(key);
if (memtable.need_flush()) {
flush_memtable();
}
}
// 查找数据
std::string get(const std::string& key) {
// 先查内存表
std::string value = memtable.get(key);
if (!value.empty()) {
return (value == "TOMBSTONE") ? "[DELETED]" : value;
}
// 再查磁盘中的 SSTable(从 Level 0 到 Level 1)
for (auto& level : levels) {
for (auto& sstable : level) {
value = sstable.read(key);
if (!value.empty()) {
return (value == "TOMBSTONE") ? "[DELETED]" : value;
}
}
}
return "[NOT FOUND]";
}
private:
// 将内存表转储为 Level 0 的 SSTable
void flush_memtable() {
auto data = memtable.export_data();
std::string filename = "sstable_level0_" + std::to_string(levels[0].size()) + ".dat";
SSTable sstable(filename);
sstable.write(data);
levels[0].push_back(sstable);
memtable.clear();
// 触发 Level 0 合并
if (levels[0].size() >= level0_max) {
compact_level(0);
}
}
// 合并层级
void compact_level(int level) {
std::vector<SSTable> merged_tables;
std::vector<std::pair<std::string, std::string>> merged_data;
// 读取当前层级所有 SSTable 的数据
for (auto& sstable : levels[level]) {
// 模拟读取所有键值对(实际需实现迭代器)
std::ifstream file(sstable.filename);
std::string line;
while (std::getline(file, line)) {
std::istringstream iss(line);
std::string key, value;
std::getline(iss, key, ',');
std::getline(iss, value);
merged_data.emplace_back(key, value);
}
file.close();
fs::remove(sstable.filename); // 删除旧文件
}
// 去重和删除标记处理
std::map<std::string, std::string> temp_map;
for (const auto& entry : merged_data) {
if (entry.second != "TOMBSTONE") {
temp_map[entry.first] = entry.second;
} else {
temp_map.erase(entry.first);
}
}
// 写入下一层级
std::vector<std::pair<std::string, std::string>> new_data(temp_map.begin(), temp_map.end());
std::string filename = "sstable_level1_" + std::to_string(levels[1].size()) + ".dat";
SSTable new_sstable(filename);
new_sstable.write(new_data);
levels[1].push_back(new_sstable);
// 清空当前层级
levels[level].clear();
}
};
4. 测试示例
#include iostream>
int main() {
LSMTree lsm;
// 插入数据
lsm.put("name", "Alice");
lsm.put("age", "30");
std::cout < "Get name: " < lsm.get("name") < std::endl; // Alice
// 删除数据
lsm.remove("age");
std::cout < "Get age: " < lsm.get("age") < std::endl; // [DELETED]
// 强制触发合并
for (int i = 0; i 5; i++) {
lsm.put("key" + std::to_string(i), "value" + std::to_string(i));
}
std::cout < "Get key3: " < lsm.get("key3") < std::endl; // value3
return 0;
}
实现说明
- MemTable:使用
std::map
模拟有序内存表,支持插入、删除和查找。 - SSTable:将数据按 CSV 格式写入文件,简化查询逻辑。
- 合并策略:当 Level 0 的 SSTable 数量超过阈值时,合并到 Level 1。
- 删除处理:通过 Tombstone 标记实现惰性删除,合并时清理数据。
优化方向
- 跳表实现:替换
std::map
为自定义跳表,提高并发写入性能。 - Bloom Filter:为 SSTable 添加布隆过滤器,加速查询。
- 多线程合并:后台线程异步执行合并操作,避免阻塞写入。
- 文件格式优化:使用二进制格式(如 Protobuf)存储 SSTable,减少 I/O 开销。
通过上述实现,可以理解 LSM-Tree 的核心设计思想,并在此基础上扩展为生产级存储引擎。