一、基础架构梳理
leveldb基本数据流程如下:
1. 接收写入请求与预写日志(WAL)记录
- 写入入口:用户调用
Put
或Delete
接口提交键值对(或批量操作WriteBatch
)。 - 原子性保证:
- 所有写入操作被封装为
WriteBatch
,确保操作的原子性(要么全部成功,要么全部失败)。
- 所有写入操作被封装为
- 预写日志(WAL):
- 操作首先以追加模式写入 LOG 文件(如
001289.log
),格式为二进制记录。 - 日志条目包含操作类型(Put/Delete)、键值数据、序列号(用于多版本控制)。
- 持久化强制:通过
fsync
(可配置)确保日志写入磁盘,防止系统崩溃导致数据丢失。
- 操作首先以追加模式写入 LOG 文件(如
- 日志文件切换:
- 当 MemTable 写满时,LOG 文件会关闭并生成新日志文件,旧日志保留至对应 MemTable 持久化后删除。
2. 写入内存表(MemTable)
- MemTable 结构:
- 基于 跳表(SkipList) 实现,支持高效的有序插入和查询(O(log n) 复杂度)。
- 所有键按字典序排列,支持范围查询。
- 写入流程:
- 写入请求解析后,键值对按格式(操作类型、键、值、序列号)插入 MemTable。
- 序列号递增:每个写入操作分配全局递增的序列号,用于实现快照隔离和旧版本数据清理。
- 内存控制:
- MemTable 大小由
write_buffer_size
参数控制(默认 4MB),写满后触发 Immutable 转换。
- MemTable 大小由
3. MemTable 写满转换为 Immutable MemTable
- 转换条件:
- 当 MemTable 大小达到阈值时,标记为 Immutable MemTable,停止接受新写入。
- 新写入由新创建的 MemTable 处理,保证写入不阻塞。
- 后台处理:
- Immutable MemTable 由后台线程异步持久化为 SSTable 文件(如
001290.ldb
)。 - 持久化前,数据按键排序并生成索引块、过滤块(Bloom Filter)等元信息。
- Immutable MemTable 由后台线程异步持久化为 SSTable 文件(如
4. 持久化到磁盘(SSTable 文件生成)
- SSTable 结构:
- 文件按层级(Level 0 到 Level N)组织,每层文件数量与大小有限制。
- Level 0 的 SSTable 由 Immutable MemTable 直接生成,允许键范围重叠;更高层级文件有序且无重叠。
- 文件生成流程:
- 排序与分块:Immutable MemTable 数据按键排序,分割为多个数据块(Block)。
- 元信息写入:生成数据块索引(记录每个块的起始键和偏移)、Bloom Filter(加速查询)、文件尾部校验和。
- 文件命名:全局递增编号(如
001290.ldb
),确保唯一性。 - 原子提交:生成完成后,将文件加入元数据管理(通过 MANIFEST 更新)。
5. 元数据更新(MANIFEST 与 CURRENT)
- MANIFEST 文件:
- 记录数据库的全局状态,包括所有 SSTable 文件、层级、键范围、压缩点等信息。
- 每次新增或删除 SSTable 时,生成一条 VersionEdit 记录,追加到 MANIFEST。
- CURRENT 文件:
- 指向当前生效的 MANIFEST 文件(如
MANIFEST-000052
)。 - 更新流程:生成新 MANIFEST 文件后,原子修改
CURRENT
文件内容为新文件名。
- 指向当前生效的 MANIFEST 文件(如
6. Compaction 过程
- 触发条件:
- Level 0 文件数过多:默认阈值(4 个文件)触发 Compaction。
- 层级大小限制:更高层级(如 Level 1+)总大小超过
max_bytes_for_level
时触发。 - 查询优化:合并重叠键范围,减少读取时的文件访问数。
- Compaction 类型:
- Minor Compaction:将 Immutable MemTable 转换为 Level 0 的 SSTable。
- Major Compaction:跨层级合并 SSTable,删除过期数据,减少冗余。
- 执行流程:
- 选择文件:根据层级策略(如大小或文件数)选择待合并的 SSTable。
- 多路归并排序:合并多个文件的键,保留最新版本(高序列号)的数据。
- 生成新文件:输出到更高层级,旧文件标记为待删除。
- 元数据更新:更新 MANIFEST,删除旧文件(实际删除可能延迟到事务提交后)。
7. 异常处理与崩溃恢复
- 日志重放:
- 重启时检查残留的
.log
文件(如001289.log
),重放未持久化到 SSTable 的操作。 - 通过序列号过滤已过期的写入,确保数据一致性。
- 重启时检查残留的
- MANIFEST 恢复:
- 读取
CURRENT
文件找到最新 MANIFEST,加载所有 SSTable 文件和层级信息。 - 校验文件完整性(通过校验和),丢弃损坏的文件。
- 读取
- 原子性保证:
- 文件删除和元数据更新通过
Rename
操作保证原子性,避免部分写入导致状态不一致。
- 文件删除和元数据更新通过
二、存储目录解析
如下为一个使用leveldb进行存储的一个数据实际存储的结构目录,目录中一般包含.ldb、.log、CURRENT、LOG、MANIFEST、LOCK等文件。其具体作用如下:
1. LOG 文件(当前日志文件)
- 作用:记录所有写操作的预写日志(Write-Ahead Log, WAL),用于崩溃恢复。
- 机制:
- 写入数据时,首先追加到
LOG
文件,确保操作持久化。 - 内存中的 MemTable 更新后,若系统崩溃,重启时通过重放
LOG
文件恢复未持久化的数据。 - 当 MemTable 写满转为 Immutable MemTable 并刷入磁盘(生成 SSTable)后,对应的
LOG
文件会被删除。
- 写入数据时,首先追加到
- 文件名:默认为
LOG
,若存在未处理的旧日志,可能保留为[0-9]+.log
(如001289.log
),在恢复后被删除。
2. CURRENT 文件
- 作用:指向当前生效的清单文件(MANIFEST)。
- 机制:
- LevelDB 每次启动或元数据变更时,可能生成新的 MANIFEST 文件(如
MANIFEST-000052
)。 CURRENT
文件仅保存最新 MANIFEST 的文件名,确保数据库知道加载哪个版本的元数据。
- LevelDB 每次启动或元数据变更时,可能生成新的 MANIFEST 文件(如
- 示例:若
CURRENT
内容为MANIFEST-000052
,则使用该文件加载元数据。
3. MANIFEST 文件
- 作用:记录数据库的元数据,包括 SSTable 文件列表、层级结构、键范围等信息。
- 机制:
- 每次 Compaction 或 MemTable 刷盘时,元数据变更会追加到 MANIFEST。
- 为避免文件过大,MANIFEST 会周期性合并(通过 Compaction),旧版本文件被删除。
- 文件名格式为
MANIFEST-[0-9]+
(如MANIFEST-000052
)。
- 内容:包含版本编辑日志(VersionEdit),记录 SSTable 的增删和层级变动。
4. [0-9]+.log 文件(历史日志文件)
- 作用:已持久化的旧日志文件,可能用于异常恢复。
- 场景:
- 若 LevelDB 非正常关闭(如崩溃),可能存在未处理的
[0-9]+.log
文件。 - 重启时,这些文件会被读取以恢复未刷入 SSTable 的数据,随后删除。
- 若 LevelDB 非正常关闭(如崩溃),可能存在未处理的
- 示例:
001289.log
表示第 1289 号日志文件。
5. [0-9]+.ldb 文件(SSTable 文件)
- 作用:存储实际数据的不可变文件,按层级组织,支持高效查询。
- 机制:
- Immutable MemTable 刷盘时生成
.ldb
文件(如001290.ldb
),文件名全局递增。 - 不同层级(Level 0 到 Level N)的 SSTable 文件通过 Compaction 合并,高层级文件范围更大且有序。
- 文件名中的数字(如
001290
)是唯一标识符,与生成顺序相关。
- Immutable MemTable 刷盘时生成
- 结构:包含数据块、元信息块、索引块等,采用前缀压缩优化存储。
其他辅助文件
- LOCK 文件:防止多进程同时访问同一数据库,通过文件锁实现。
- 临时文件:操作中生成的临时文件(如
*.dbtmp
),完成后删除或重命名。