强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

LevelDB 完全指南 / 第 3 章 · 架构总览

第 3 章 · 架构总览

3.1 整体架构

LevelDB 的架构可以用一张全景图概括:

                        ┌─────────────────────────────┐
                        │         用户写入             │
                        └──────────┬──────────────────┘
                                   │
                                   ▼
                    ┌──────────────────────────────┐
                    │     Write-Ahead Log (WAL)     │ ← 持久化保障
                    └──────────────────────────────┘
                                   │
                                   ▼
                    ┌──────────────────────────────┐
                    │        MemTable (内存)         │ ← 跳表实现
                    │   ┌──────────────────────┐    │
                    │   │  Active MemTable     │    │ ← 可写
                    │   ├──────────────────────┤    │
                    │   │  Immutable MemTable  │    │ ← 只读,等待刷盘
                    │   └──────────────────────┘    │
                    └──────────────┬───────────────┘
                                   │ Flush
                                   ▼
        ┌──────────────────────────────────────────────────┐
        │                    SSTable 文件 (磁盘)             │
        │  ┌────────────────────────────────────────────┐  │
        │  │  Level 0  (直接从 MemTable 刷出)            │  │
        │  │  ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐     │  │
        │  │  │SST-1 │ │SST-2 │ │SST-3 │ │SST-4 │     │  │
        │  │  └──────┘ └──────┘ └──────┘ └──────┘     │  │
        │  ├────────────────────────────────────────────┤  │
        │  │  Level 1  (Compaction 后的有序数据)         │  │
        │  │  ┌────────────────┐ ┌────────────────┐    │  │
        │  │  │    SST-5       │ │    SST-6       │    │  │
        │  │  └────────────────┘ └────────────────┘    │  │
        │  ├────────────────────────────────────────────┤  │
        │  │  Level 2  (更大的有序数据集)                │  │
        │  │  ┌──────────────────────────────────┐     │  │
        │  │  │           SST-7                   │     │  │
        │  │  └──────────────────────────────────┘     │  │
        │  ├────────────────────────────────────────────┤  │
        │  │  ...                                      │  │
        │  │  Level 6  (最大层)                        │  │
        │  └────────────────────────────────────────────┘  │
        └──────────────────────────────────────────────────┘

核心组件一览

组件位置作用数据结构
WAL磁盘崩溃恢复顺序日志文件
MemTable内存缓冲写入跳表(SkipList)
Immutable MemTable内存等待刷盘跳表(SkipList)
Table Cache内存缓存打开的 SSTable 文件句柄LRU Cache
Block Cache内存缓存热数据块LRU Cache
VersionSet内存管理所有 SSTable 文件的元信息Version 链表
Manifest磁盘记录 SSTable 文件的增删操作VersionEdit 日志
SSTable磁盘存储实际数据排序的键值对文件
Current磁盘指向当前活跃的 Manifest 文件单行文本文件

3.2 MemTable

MemTable 是 LevelDB 的内存写缓冲区,所有写操作首先在这里完成。

数据结构:跳表(SkipList)

Level 3: HEAD ────────────────────────────────────→ 90 ──→ NIL
Level 2: HEAD ──────────────→ 30 ─────────────────→ 90 ──→ NIL
Level 1: HEAD ──→ 10 ───────→ 30 ──→ 50 ──────────→ 90 ──→ NIL
Level 0: HEAD ──→ 10 ──→ 20 ──→ 30 ──→ 40 ──→ 50 ──→ 60 ──→ 70 ──→ 80 ──→ 90 ──→ NIL

为什么选择跳表而不是红黑树?

对比项跳表红黑树
查找复杂度O(log n)O(log n)
实现复杂度简单复杂
范围扫描天然支持(链表遍历)需要额外逻辑
并发友好更容易实现无锁CAS 旋转复杂
缓存友好较好较差(指针多)
内存开销平均 1.33x2x(颜色位+指针)

MemTable 内部格式

每个键值对在 MemTable 中的存储格式:

┌──────────────┬────────────┬─────────────┬──────────────┬────────────┐
│ key_size     │ key        │ value_size  │ value        │ 标记       │
│ (varint32)   │ (InternalKey)│ (varint32)│ (值)         │ (Put/Del)  │
└──────────────┴────────────┴─────────────┴──────────────┴────────────┘

InternalKey 的格式:

┌──────────────────────┬──────────────────┬──────────┐
│ user_key             │ sequence number  │ type     │
│ (用户提供的 Key)      │ (7 字节)         │ (1 字节) │
└──────────────────────┴──────────────────┴──────────┘

关键参数

参数默认值说明
write_buffer_size4 MB单个 MemTable 的最大大小
max_write_buffer_number2最大 MemTable 数量(含 Immutable)
min_write_buffer_number_to_merge1刷盘前最少合并的 MemTable 数

💡 提示:增大 write_buffer_size 可以减少 Minor Compaction 频率,但会增加内存占用和崩溃恢复时间。


3.3 Write-Ahead Log (WAL)

WAL 是 LevelDB 崩溃恢复的核心保障。任何写入在到达 MemTable 之前,必须先持久化到 WAL。

WAL 文件格式

┌─────────────────────────────────────────────────────────┐
│  Record #1                                              │
│  ┌────────┬────────┬────────┬───────────┬────────────┐  │
│  │ CRC32  │ Length │ Type   │ Data      │ Padding    │  │
│  │ (4B)   │ (2B)   │ (1B)   │ (变长)    │ (对齐到)   │  │
│  └────────┴────────┴────────┴───────────┴────────────┘  │
├─────────────────────────────────────────────────────────┤
│  Record #2                                              │
│  ┌────────┬────────┬────────┬───────────┬────────────┐  │
│  │ CRC32  │ Length │ Type   │ Data      │ Padding    │  │
│  └────────┴────────┴────────┴───────────┴────────────┘  │
├─────────────────────────────────────────────────────────┤
│  ...                                                    │
│  Block size = 32KB                                      │
└─────────────────────────────────────────────────────────┘

Record Type

类型说明
kZeroType0预留
kFullType1完整记录(数据在一个 Record 中)
kFirstType2分片记录的第一片
kMiddleType3分片记录的中间片
kLastType4分片记录的最后一片

WAL 工作流程

写入流程:
  1. 用户调用 db->Put("key", "value")
  2. 构造 InternalKey = "key" + seq(7) + type(Put)
  3. 序列化为 WAL Record
  4. 追加写入当前 WAL 文件(顺序 I/O)
  5. 如果 sync=true,调用 fsync()
  6. 插入 MemTable

崩溃恢复流程:
  1. 打开数据库,读取 MANIFEST 文件确定最新 Version
  2. 找到未归档的 WAL 文件
  3. 逐条回放 WAL Record
  4. 重建 MemTable
  5. 触发 Flush 将 MemTable 刷到 SSTable

WAL 关键参数

参数默认值说明
WriteOptions::syncfalse每次写入后是否 fsync

⚠️ 注意sync=false 时,写入只保证到达 OS 缓冲区,不保证持久化到磁盘。如果机器突然断电,可能丢失最近几秒的数据。生产环境建议根据数据重要性权衡。


3.4 SSTable(Sorted String Table)

SSTable 是 LevelDB 在磁盘上的数据存储格式,文件后缀为 .ldb(或旧版本的 .sst)。

SSTable 文件结构

┌───────────────────────────────────────────────┐
│              Data Block #1                     │
│  ┌──────────────────────────────────────────┐ │
│  │  Entry₁ │ Entry₂ │ ... │ Entryₙ         │ │
│  └──────────────────────────────────────────┘ │
│  [重启点偏移量数组]                             │
├───────────────────────────────────────────────┤
│              Data Block #2                     │
│  ...                                          │
├───────────────────────────────────────────────┤
│              ...                               │
├───────────────────────────────────────────────┤
│              Meta Block (Bloom Filter)         │
│  ┌──────────────────────────────────────────┐ │
│  │  Bloom Filter 位数组                     │ │
│  └──────────────────────────────────────────┘ │
├───────────────────────────────────────────────┤
│              Meta Index Block                  │
│  ┌──────────────────────────────────────────┐ │
│  │  "filter" → Meta Block 位置              │ │
│  └──────────────────────────────────────────┘ │
├───────────────────────────────────────────────┤
│              Index Block                       │
│  ┌──────────────────────────────────────────┐ │
│  │  最大Key₁ → Data Block #1 位置           │ │
│  │  最大Key₂ → Data Block #2 位置           │ │
│  │  ...                                     │ │
│  └──────────────────────────────────────────┘ │
├───────────────────────────────────────────────┤
│              Footer (固定 48 字节)             │
│  ┌──────────────────────────────────────────┐ │
│  │  Meta Index Block 偏移+大小              │ │
│  │  Index Block 偏移+大小                    │ │
│  │  Magic Number (0xdb4775248b80fb57)       │ │
│  └──────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘

Data Block 内部格式

┌─────────────┬─────────────┬───────────┬──────────┬───────────┐
│ Shared Len  │ Non-Shared  │ Value Len │ Key Suffix│ Value     │
│ (varint)    │ Len (varint)│ (varint)  │ (字节)    │ (字节)    │
└─────────────┴─────────────┴───────────┴──────────┴───────────┘

前缀压缩示例:
  Key₁ = "user:1001:name"  → 存储完整 key
  Key₂ = "user:1001:email" → shared=10, non_shared=5, suffix="email"
  Key₃ = "user:1002:name"  → shared=5,  non_shared=9, suffix="1002:name"

SSTable 关键参数

参数默认值说明
block_size4096 (4KB)Data Block 大小
block_restart_interval16每隔多少个 Entry 做一次前缀压缩重启
compressionkSnappyCompression压缩算法

文件命名规则

Level 0:  /data/000001.ldb, 000002.ldb, ...
Level 1:  /data/000005.ldb, 000008.ldb, ...
Level 2:  /data/000012.ldb, ...
...
Level 6:  /data/000045.ldb

WAL:      /data/000003.log
Manifest: /data/MANIFEST-000002
Current:  /data/CURRENT

3.5 Level 层级结构

SSTable 文件按 Level 组织,从 Level 0 到 Level 6:

Level最大总大小SSTable 数量Key 范围关系
0~10 MB≤ 4 个文件可重叠
1~10 MB不限不重叠
2~100 MB不限不重叠
3~1 GB不限不重叠
4~10 GB不限不重叠
5~100 GB不限不重叠
6~1 TB不限不重叠

关键特点:

  • Level 0 的 SSTable 之间 Key 范围可能重叠(因为直接从 MemTable 刷出)
  • Level 1+ 的 SSTable 之间 Key 范围严格不重叠(Compaction 保证)
  • 相邻 Level 之间大小比约为 10 倍max_bytes_for_level_multiplier
Level 0:  [a──────f] [c──────i] [b──────g]   ← Key 范围重叠
Level 1:  [a───c] [d───f] [g───i]            ← Key 范围不重叠
Level 2:  [a─b] [c─d] [e─f] [g─h] [i─j]     ← Key 范围不重叠

查找流程

查找 Key="user:1001:name":

1. 查找 MemTable → 未命中
2. 查找 Immutable MemTable → 未命中
3. 查找 Level 0 SSTable(可能需要查多个文件,按新→旧顺序)
   → 在 SST-2 中找到?返回
4. 查找 Level 1(二分定位 SSTable)
   → SST-5 包含 [d───f],Key 在范围内 → 读取并查找
5. 未找到则继续 Level 2, 3, ..., 6

3.6 Version 与 MANIFEST

Version

每个 Version 代表某一时刻数据库中所有 SSTable 文件的快照。LevelDB 使用 Version 链表管理历史版本:

VersionSet
  └── current_ → Version-5
                   ├── Level 0: {000001.ldb, 000002.ldb}
                   ├── Level 1: {000005.ldb}
                   └── Level 2: {000008.ldb, 000012.ldb}
                 Version-4
                   ├── ...
                 Version-3
                   └── ...

MANIFEST 文件

MANIFEST 文件记录了 Version 的变更历史(VersionEdit 序列化日志):

MANIFEST-000002:
  ┌───────────────────────────────────┐
  │ Edit #1:                          │
  │   log_number = 3                  │
  │   add: Level 0, file 000001.ldb  │
  │   delete: (none)                  │
  ├───────────────────────────────────┤
  │ Edit #2:                          │
  │   log_number = 5                  │
  │   add: Level 1, file 000005.ldb  │
  │   delete: Level 0, file 000001   │
  └───────────────────────────────────┘

CURRENT 文件

$ cat /data/CURRENT
MANIFEST-000002

单行文本文件,指向当前活跃的 MANIFEST 文件。


3.7 读写路径全景

写入路径

用户调用 db->Put(key, value)
    │
    ▼
┌──────────────────────┐
│ 1. 加写锁            │    ← 保证单写者
│ 2. 分配 Sequence No. │
│ 3. 构造 InternalKey  │
└──────────┬───────────┘
           │
           ▼
┌──────────────────────┐
│ 4. 写入 WAL          │    ← 崩溃恢复保障
│    (可选: fsync)     │
└──────────┬───────────┘
           │
           ▼
┌──────────────────────┐
│ 5. 插入 MemTable     │    ← 内存操作
└──────────┬───────────┘
           │
           ▼
┌──────────────────────────────────────┐
│ 6. 检查 MemTable 是否超过阈值         │
│    是 → 将当前 MemTable 设为 Immutable │
│         创建新 MemTable + 新 WAL      │
│         后台线程将 Immutable Flush    │
└──────────────────────────────────────┘

读取路径

用户调用 db->Get(key, &value)
    │
    ▼
┌──────────────────────────────────┐
│ 1. 加读锁(允许并发读)           │
│ 2. 获取当前 Version              │
│ 3. 构造 LookupKey                │
└──────────┬───────────────────────┘
           │
           ▼
┌──────────────────────────────────┐
│ 4. 查找 MemTable                 │ ← 找到则返回
│ 5. 查找 Immutable MemTable       │ ← 找到则返回
└──────────┬───────────────────────┘
           │
           ▼
┌──────────────────────────────────┐
│ 6. 使用 Table Cache 查找 SSTable │
│    Level 0: 遍历所有文件(新→旧)   │
│    Level 1-6: 二分查找定位文件    │
│                                  │
│    对每个 SSTable:               │
│    a. 检查 Bloom Filter → 跳过? │
│    b. 查找 Index Block → 定位   │
│       Data Block                 │
│    c. 读取 Data Block → 查找    │
│    d. 找到 → 检查类型            │
│       Put → 返回 value           │
│       Delete → 返回 NotFound     │
└──────────┬───────────────────────┘
           │
           ▼
┌──────────────────────────────────┐
│ 7. 所有层未找到 → 返回 NotFound  │
└──────────────────────────────────┘

3.8 并发模型

LevelDB 的并发模型非常简单:

┌───────────────────────────────────────────────┐
│                  LevelDB                        │
│                                                │
│   ┌────────────┐    ┌──────────────────────┐   │
│   │ Writer     │    │ Reader 1             │   │
│   │ (单线程)    │    │ Reader 2             │   │
│   │            │    │ Reader 3             │   │
│   │ 互斥锁     │    │ ...                  │   │
│   │ 保证串行    │    │ 共享读锁(无锁)      │   │
│   └────────────┘    └──────────────────────┘   │
│                                                │
│   ┌──────────────────────────────────────────┐ │
│   │ Background Thread                        │ │
│   │ (Compaction / Flush)                     │ │
│   └──────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘
操作线程模型说明
写入单线程串行所有写入排队,一个接一个执行
读取多线程并发使用引用计数,无需加锁
Compaction后台单线程异步执行,不阻塞读写
Flush后台单线程异步将 Immutable MemTable 刷盘

⚠️ 注意:LevelDB 不支持并发写入。如果写入成为瓶颈,考虑使用 RocksDB(支持多线程写)。


3.9 关键文件汇总

文件作用生命周期
*.logWAL 日志文件写满后归档,Compaction 后删除
*.ldbSSTable 数据文件Compaction 生成新文件后旧文件删除
MANIFEST-*VersionEdit 日志新建 MANIFEST 后旧的归档
CURRENT指向当前 MANIFEST持久存在
LOCK文件锁,防止多进程打开持久存在
LOG / LOG.old诊断日志轮转

3.10 本章小结

组件核心要点
MemTable跳表实现,4MB 默认大小,所有写入先到这里
WAL顺序写日志,保证崩溃一致性
SSTable多层结构,前缀压缩,支持 Bloom Filter
LevelL0 Key 可重叠,L1+ 严格有序,相邻层 10 倍递增
Version/MANIFEST记录 SSTable 文件变更历史

扩展阅读

  1. LevelDB 源码db/db_impl.cc,核心实现
  2. SSTable 格式table/format.cc,SSTable 文件结构
  3. 跳表实现db/skiplist.h,无锁跳表
  4. Bigtable 论文:Google, 2006 — SSTable 的源头
  5. WiscKey 论文:USENIX FAST 2016 — Value 分离存储优化

第 2 章 · 编译安装与语言绑定 | 第 4 章 · 基本操作