LevelDB源码分析
Intro
我们从LevelDB给定的示例入手对LevelDB的源代码进行分析,本篇仅分析其中的Open函数,该函数用于打开一个数据库。
1 | leveldb::DB* db; |
Open
Open函数为DB类的一个静态函数。该函数负责打开一个数据库,由于DB仅为一个接口,其中定义了LevelDB所提供的接口,而实际的数据库实现放在了DBImpl类中,DBImpl由DB类继承而来。
首先需要介绍一下LevelDB的构成,LevelDB当前的状态由一个Version构成,每个Version都追踪着一系列用于构成数据库的文件,这些Version共同构成VersionSet,当某些文件被添加或者删除时就会触发Version之间的变换,Version之间的变换可以使用VersionEdit来记录不同Version之间的文件或其他元信息的差异。
接下来逐行分析Open这个函数。
1 | Status DB::Open(const Options& options, const std::string& dbname, DB** dbptr) { |
DBImpl::Recover
从已有的文件中恢复数据库的信息
1 | Status DBImpl::Recover(VersionEdit* edit, bool *save_manifest) { |
NewDB()
创建新的数据库
1 | Status DBImpl::NewDB() { |
DBImpl::RecoverLogFile
由日志文件中恢复未被写入各个Level文件的更新。save_manifest用来标识是否有新的文件被加入各个Level,如果有的话则需要更新当前Version,进而需要更新MANIFEST文件。
1 | Status DBImpl::RecoverLogFile(uint64_t log_number, bool last_log, |
VersionEdit
VersionEdit用以修改当前Version的信息,包括文件编号、日志编号、新增/删除的文件等,该类提供了EncodeTo和DecodeFrom来与string进行相互转换。以下是VersionEdit提供的接口
1 | class VersionEdit { |
Version
Version用于记录当前数据库所拥有的各个Level的文件以及一些有关Compaction的信息。该类还提供了接口用于在这些文件上进行查找。各个Version均使用引用计数来防止被过早的释放,当一个Version被加入VersionSet时会增加引用计数,当VersionSet的PickCompaction以及CompactRange选择一个Version作为输入时也会增加对应Version的引用计数,使用Builder来构建新的Version时也会增加相应的引用计数。
VersionSet
VersionSet用于管理当前数据库不同的Version,所有Version被串成一个双向链表。不同Version之间的变化可以通过VersionSet提供的LogAndApply来应用VersionEdit或者使用VersionSet内部提供的Builder来一次进行多个VersionEdit的变更。
接下来介绍一下DBImpl::Recover中使用的VersionSet::Recover函数
1 | Status VersionSet::Recover(bool *save_manifest) { |
总结
Open函数的流程可以概括为如下步骤:
- 构造DBImpl对象
- 从已有文件中恢复信息
- 根据是否存在CURRENT文件判断是否是已有的数据库,若没有则创建新数据库
- 从已有文件中恢复VersionSet信息
- 利用CURRENT找到当前MANIFEST文件
- 从MANIFEST文件中获取所有VersionEdit的信息
- 利用所有的VersionEdit信息来生成当前Version的信息
- 找到所有日志文件
- 从所有日志文件中读取所有记录,插入一个MemTable中,该MemTable大小超过定值后会写入Level0的Table中
- 最后一个日志文件可能被复用,其对应的MemTable会作为当前数据库的MemTable
- MANIFEST文件可能也会复用,如果不复用则在第一次调用LogAndApply时会创建新的MANIFEST文件
- 如果没有可以复用的日志文件及MemTable,则创建新的日志文件及MemTable
- 如果恢复过程中生成了新的文件,即生成了新的Level0的Table文件或者需要新的MANIFEST文件,则将VersionEdit记录至VersionSet中
- 删除无用文件
- 调度Compaction