设计 KV 存储
问题
如何用 Rust 设计一个键值存储引擎?
答案
架构设计
简化的 KV Store 实现
use std::collections::BTreeMap;
use std::sync::{Arc, RwLock};
use std::fs::{File, OpenOptions};
use std::io::Write;
/// 基于 BTreeMap + WAL 的简易 KV 存储
pub struct KvStore {
memtable: Arc<RwLock<BTreeMap<String, Option<String>>>>,
wal: std::sync::Mutex<File>,
}
impl KvStore {
pub fn open(path: &str) -> std::io::Result<Self> {
let wal = OpenOptions::new()
.create(true)
.append(true)
.open(format!("{}/wal.log", path))?;
Ok(Self {
memtable: Arc::new(RwLock::new(BTreeMap::new())),
wal: std::sync::Mutex::new(wal),
})
}
pub fn set(&self, key: String, value: String) -> std::io::Result<()> {
// 1. 先写 WAL(保证持久性)
{
let mut wal = self.wal.lock().unwrap();
writeln!(wal, "SET {} {}", key, value)?;
wal.flush()?;
}
// 2. 再写 MemTable
let mut table = self.memtable.write().unwrap();
table.insert(key, Some(value));
Ok(())
}
pub fn get(&self, key: &str) -> Option<String> {
let table = self.memtable.read().unwrap();
table.get(key).and_then(|v| v.clone())
}
pub fn delete(&self, key: &str) -> std::io::Result<()> {
// 墓碑标记(写入 None)
{
let mut wal = self.wal.lock().unwrap();
writeln!(wal, "DEL {}", key)?;
}
let mut table = self.memtable.write().unwrap();
table.insert(key.to_string(), None);
Ok(())
}
}
存储引擎对比
| 引擎 | 数据结构 | 写性能 | 读性能 | 适用场景 |
|---|---|---|---|---|
| B-Tree | B+ 树 | 中等 | 优秀 | 读多写少 |
| LSM-Tree | MemTable + SSTable | 优秀 | 中等 | 写多读少 |
| Bitcask | Hash Index + Log | 优秀 | 优秀(内存索引) | 数据量 < 内存 |
Rust KV 存储生态
| 库 | 引擎 | 特点 |
|---|---|---|
sled | B-Tree (lock-free) | 纯 Rust,嵌入式 |
rocksdb (绑定) | LSM-Tree | Facebook 出品,生产级 |
redb | B-Tree | 简单、安全、ACID |
常见面试问题
Q1: 为什么 LSM-Tree 写性能比 B-Tree 好?
答案:
- B-Tree:每次写可能触发页分裂,涉及随机 IO
- LSM-Tree:写入先到内存的 MemTable(有序),满后顺序写到磁盘 SSTable
顺序写比随机写快 100 倍以上。代价是读取时可能需要查多层 SSTable(通过布隆过滤器优化)。