1. 架构概述
LevelDB采用LSM-Tree(Log-Structured Merge-Tree)架构,将随机写入转换为顺序写入,通过多级存储结构实现高性能的键值存储。整个系统设计遵循"写入优化"的原则,牺牲一定的读取性能来换取卓越的写入性能。
2. 核心架构组件
2.1 分层架构视图
graph TB
subgraph "用户接口层"
A[DB Interface] --> B[DBImpl]
end
subgraph "内存存储层"
B --> C[MemTable]
B --> D[Immutable MemTable]
end
subgraph "持久化层"
B --> E[WAL Log]
B --> F[SSTable Files]
end
subgraph "元数据管理层"
B --> G[VersionSet]
G --> H[Version]
G --> I[MANIFEST]
end
subgraph "工具层"
J[TableCache] --> F
K[BlockCache] --> F
L[Comparator] --> C
L --> F
M[FilterPolicy] --> F
end
subgraph "系统层"
N[Env] --> E
N --> F
N --> I
end
2.2 数据流向图
flowchart TD
A[写入请求] --> B{检查MemTable空间}
B -->|空间足够| C[写入WAL日志]
C --> D[写入MemTable]
D --> E[返回成功]
B -->|空间不足| F[切换MemTable]
F --> G[启动后台压缩]
G --> C
H[读取请求] --> I[查找MemTable]
I -->|找到| J[返回结果]
I -->|未找到| K[查找Immutable MemTable]
K -->|找到| J
K -->|未找到| L[查找SSTable]
L --> M[Level 0]
M -->|未找到| N[Level 1]
N -->|未找到| O[Level N]
O --> P[返回NotFound/结果]
3. 核心模块详解
3.1 内存存储模块
MemTable架构
classDiagram
class MemTable {
+SkipList~char*, KeyComparator~ table_
+Arena arena_
+int refs_
+Add(seq, type, key, value)
+Get(key, value, status)
+NewIterator()
+ApproximateMemoryUsage()
}
class SkipList {
+Node* head_
+Random rnd_
+KeyComparator compare_
+Insert(key)
+Contains(key)
+NewIterator()
}
class Arena {
+vector~char*~ blocks_
+size_t alloc_ptr_
+size_t alloc_bytes_remaining_
+Allocate(bytes)
+AllocateAligned(bytes)
}
MemTable --> SkipList
MemTable --> Arena
SkipList --> Arena
MemTable特性分析:
- 数据结构: 使用跳表(SkipList)实现,支持高效的有序插入和查找
- 内存管理: Arena内存池避免频繁的malloc/free操作
- 并发控制: 支持单写多读,通过引用计数管理生命周期
- 内存限制: 默认4MB大小,超过后切换为Immutable状态
3.2 持久化存储模块
SSTable文件结构
graph TD
subgraph "SSTable文件格式"
A[Data Block 1] --> B[Data Block 2]
B --> C[Data Block N]
C --> D[MetaIndex Block]
D --> E[Index Block]
E --> F[Footer]
end
subgraph "Block内部结构"
G[Record 1] --> H[Record 2]
H --> I[Record N]
I --> J[Restart Points]
J --> K[Restart Count]
K --> L[CRC32]
end
subgraph "记录格式"
M[Shared Key Len] --> N[Unshared Key Len]
N --> O[Value Len]
O --> P[Unshared Key]
P --> Q[Value]
end
多级存储结构
graph LR
subgraph "Level 0"
A[File 1]
B[File 2]
C[File 3]
D[File 4]
end
subgraph "Level 1"
E[File A]
F[File B]
G[File C]
end
subgraph "Level 2"
H[File X]
I[File Y]
J[File Z]
K[File W]
end
A -.-> E
A -.-> F
B -.-> E
C -.-> F
C -.-> G
E -.-> H
F -.-> I
F -.-> J
G -.-> K
style A fill:#ff9999
style B fill:#ff9999
style C fill:#ff9999
style D fill:#ff9999
style E fill:#99ccff
style F fill:#99ccff
style G fill:#99ccff
style H fill:#99ff99
style I fill:#99ff99
style J fill:#99ff99
style K fill:#99ff99
存储特性分析:
- Level 0: 文件可能重叠,直接从MemTable压缩得到
- Level 1+: 文件不重叠,有序排列,便于查找
- 容量递增: 每层容量是上一层的10倍(默认配置)
- 压缩策略: 自动触发多级压缩,保持系统性能
3.3 版本管理模块
Version和VersionSet关系
classDiagram
class VersionSet {
+string dbname_
+Options* options_
+TableCache* table_cache_
+uint64_t next_file_number_
+uint64_t manifest_file_number_
+uint64_t last_sequence_
+Version* current_
+LogAndApply(edit)
+NewFileNumber()
+PickCompaction()
}
class Version {
+VersionSet* vset_
+Version* next_
+Version* prev_
+vector~FileMetaData*~ files_[kNumLevels]
+int refs_
+Get(options, key, value)
+AddIterators(options, iters)
+UpdateStats(stats)
}
class VersionEdit {
+string comparator_name_
+uint64_t log_number_
+uint64_t next_file_number_
+SequenceNumber last_sequence_
+vector~pair~int, InternalKey~~ compact_pointers_
+set~pair~int, uint64_t~~ deleted_files_
+vector~pair~int, FileMetaData~~ new_files_
+EncodeTo(dst)
+DecodeFrom(src)
}
class FileMetaData {
+uint64_t number
+uint64_t file_size
+InternalKey smallest
+InternalKey largest
+int refs
+int allowed_seeks
}
VersionSet --> Version : manages
Version --> FileMetaData : contains
VersionSet --> VersionEdit : applies
VersionEdit --> FileMetaData : modifies
3.4 压缩管理模块
压缩触发机制
graph TD
A[写入操作] --> B{MemTable满?}
B -->|是| C[Minor Compaction]
B -->|否| D[继续写入]
C --> E[创建Level 0 SSTable]
E --> F{Level 0文件数 > 4?}
F -->|是| G[触发Major Compaction]
F -->|否| H[等待下次写入]
G --> I{选择压缩Level}
I --> J[Level N Size > Limit?]
J -->|是| K[压缩Level N到N+1]
J -->|否| L[检查下一Level]
K --> M[选择重叠文件]
M --> N[合并排序写入新文件]
N --> O[更新Version]
O --> P[删除旧文件]
压缩算法流程
sequenceDiagram
participant BG as 后台线程
participant VS as VersionSet
participant C as Compaction
participant TC as TableCache
participant FS as 文件系统
BG->>VS: PickCompaction()
VS-->>BG: 返回Compaction对象
alt 有压缩任务
BG->>C: 获取输入文件列表
BG->>TC: 创建合并迭代器
loop 遍历所有键值对
BG->>BG: 检查是否应该输出
BG->>FS: 写入新SSTable文件
end
BG->>VS: LogAndApply(VersionEdit)
VS->>FS: 更新MANIFEST文件
BG->>FS: 删除旧文件
else 无压缩任务
BG->>BG: 等待下次调度
end
4. 关键算法分析
4.1 跳表算法
跳表是MemTable的核心数据结构,提供O(log n)的查找性能:
// 文件: db/skiplist.h
template<typename Key, class Comparator>
class SkipList {
private:
struct Node {
Key const key;
Node* Next(int n) {
return reinterpret_cast<Node*>(next_[n].load(std::memory_order_acquire));
}
void SetNext(int n, Node* x) {
next_[n].store(x, std::memory_order_release);
}
std::atomic<Node*> next_[1]; // 实际大小由层数决定
};
Node* head_;
std::atomic<int> max_height_;
Random rnd_;
public:
void Insert(const Key& key) {
Node* prev[kMaxHeight];
Node* x = FindGreaterOrEqual(key, prev);
int height = RandomHeight();
if (height > GetMaxHeight()) {
for (int i = GetMaxHeight(); i < height; i++) {
prev[i] = head_;
}
max_height_.store(height, std::memory_order_relaxed);
}
x = NewNode(key, height);
for (int i = 0; i < height; i++) {
x->SetNext(i, prev[i]->Next(i));
prev[i]->SetNext(i, x);
}
}
};
4.2 LSM压缩算法
LevelDB的压缩算法确保各Level的大小控制在合理范围:
// 文件: db/version_set.cc
Compaction* VersionSet::PickCompaction() {
Compaction* c;
int level;
// 基于大小的压缩
const bool size_compaction = (current_->compaction_score_ >= 1);
// 基于查找的压缩
const bool seek_compaction = (current_->file_to_compact_ != nullptr);
if (size_compaction) {
level = current_->compaction_level_;
c = new Compaction(&options_, level);
// 选择要压缩的文件
for (size_t i = 0; i < current_->files_[level].size(); i++) {
FileMetaData* f = current_->files_[level][i];
if (compact_pointer_[level].empty() ||
icmp_.Compare(f->largest.Encode(), compact_pointer_[level]) > 0) {
c->inputs_[0].push_back(f);
break;
}
}
} else if (seek_compaction) {
level = current_->file_to_compact_level_;
c = new Compaction(&options_, level);
c->inputs_[0].push_back(current_->file_to_compact_);
} else {
return nullptr;
}
SetupOtherInputs(c);
return c;
}
5. 性能优化设计
5.1 写入路径优化
graph LR
A[用户写入] --> B[WriteBatch批量化]
B --> C[WAL顺序写入]
C --> D[MemTable内存写入]
D --> E[后台异步压缩]
style C fill:#90EE90
style D fill:#90EE90
style E fill:#FFB6C1
5.2 读取路径优化
graph TD
A[用户读取] --> B[MemTable查找]
B -->|命中| F[返回结果]
B -->|未命中| C[Immutable MemTable查找]
C -->|命中| F
C -->|未命中| D[布隆过滤器检测]
D -->|可能存在| E[SSTable查找]
D -->|一定不存在| G[返回NotFound]
E --> H[Block Cache查找]
H -->|命中| F
H -->|未命中| I[磁盘读取]
I --> F
style B fill:#90EE90
style C fill:#90EE90
style D fill:#FFB6C1
style H fill:#87CEEB
6. 并发控制机制
6.1 读写并发
- 写入串行化: 所有写入操作通过单一队列串行化执行
- 读写并行: 读操作不阻塞写操作,通过MVCC机制实现
- 快照隔离: 使用序列号实现快照隔离级别
6.2 后台压缩并发
sequenceDiagram
participant W as 写入线程
participant M as MemTable
participant B as 后台线程
participant S as SSTable
W->>M: 写入数据
W->>W: 检查MemTable大小
alt MemTable满
W->>M: 切换为Immutable
W->>M: 创建新MemTable
W->>B: 通知压缩
end
par 并行执行
W->>M: 继续写入新MemTable
and
B->>M: 读取Immutable MemTable
B->>S: 创建新SSTable
B->>B: 删除Immutable MemTable
B->>B: 检查是否需要Major压缩
end
7. 容错和恢复机制
7.1 WAL日志恢复
flowchart TD
A[数据库启动] --> B[扫描WAL文件]
B --> C[按序列号排序]
C --> D[重放日志记录]
D --> E{记录类型?}
E -->|Put| F[插入MemTable]
E -->|Delete| G[标记删除MemTable]
F --> H[继续下一条记录]
G --> H
H --> I{日志结束?}
I -->|否| D
I -->|是| J[创建新日志文件]
J --> K[启动完成]
7.2 MANIFEST恢复
- 版本历史: MANIFEST记录所有版本变更
- 检查点: 定期写入完整版本信息
- 增量恢复: 从最近检查点开始应用增量变更
8. 架构优势分析
8.1 写入优势
- 顺序写入: WAL和SSTable都是顺序写入,充分利用磁盘性能
- 批量写入: WriteBatch减少系统调用开销
- 异步压缩: 压缩在后台执行,不阻塞前台写入
8.2 存储优势
- 分层存储: 热数据在上层,冷数据在下层
- 增量压缩: 只压缩重叠的文件,减少IO
- 空间回收: 自动清理过期数据和文件
8.3 查询优势
- 内存优先: 新数据在MemTable中,查询效率高
- 索引优化: 每个SSTable都有索引,支持快速定位
- 缓存机制: 多级缓存提升随机读性能
这种架构设计使得LevelDB在保持简单性的同时,实现了出色的写入性能和合理的读取性能,特别适合写多读少的应用场景。