Content
这次我们讲解的主题是:创建启动数据库
需要回答的问题: 1. 创建数据库的流程是什么?创建数据库时创建了哪些文件,都有什么作用? 2. DBImpl类内都有哪些成员?其作用是什么? 3. DB::Open函数的实现。 4. DBImpl::Recover函数的实现。 5. DBImpl::NewDB函数的实现。 6. VersionSet::Recover函数的实现。CURRENT的作用是什么?
这次我们的分工:我后三个,林神前三个
林神的参考:https://zhuanlan.zhihu.com/p/340804308
https://leveldb-handbook.readthedocs.io/zh/latest/version.html
1.创建数据库的流程是什么?创建数据库时创建了哪些文件,都有什么作用?¶
LevelDB无论是创建数据库,还是打开现有的数据库,都是使用Open方法。DB::Open(db/db_impl.cc) https://www.cnblogs.com/mh1092/p/9951342.html
Status DB::Open(const Options& options, const std::string& dbname, DB** dbptr) { *dbptr = NULL; DBImpl* impl = new DBImpl(options, dbname);
impl->mutex_.Lock();
VersionEdit edit;
bool save_manifest = false;
// Recover逻辑,如果存在数据库,则Load数据库数据,并对日志进行恢复,否则,创建新的数据库
Status s = impl->Recover(&edit, &save_manifest); //具体可看recover那篇博文
if (s.ok() && impl->mem_ == NULL) {
//impl->mem_ == NULL说明没有继续使用旧日志需创建新的日志
// 从version_set获取一个新的文件序号用于日志文件,所以如果是新建的数据库,则第一个LOG的
//序号为2(1已经被MANIFEST占用,NewDB代码里可以看出)
uint64_t new_log_number = impl->versions_->NewFileNumber();
// 记录日志文件号,创建新的log文件及Writer对象
WritableFile* lfile;
s = options.env->NewWritableFile(LogFileName(dbname, new_log_number), &lfile); if (s.ok()) {
edit.SetLogNumber(new_log_number);
impl->logfile_ = lfile;
impl->logfile_number_ = new_log_number;
impl->log_ = new log::Writer(lfile);
impl->mem_ = new MemTable(impl->internal_comparator_);
impl->mem_->Ref();
}
}
if (s.ok()&& save_manifest) {
//save_manifest说明有新的sst文件生成,需要记录在manifest文件
edit.SetPrevLogNumber(0); // No older logs needed after recovery.
edit.SetLogNumber(impl->logfile_number_);
//产生了新log,就log已经回放完毕了,应用edit生成新版本
s = impl->versions_->LogAndApply(&edit, &impl->mutex_);
}
if(s.ok())
{
// 删除废弃的文件(如果存在)
impl->DeleteObsoleteFiles();
// 检查是否需要Compaction,如果需要,则让后台启动Compaction线程
impl->MaybeScheduleCompaction();
}
impl->mutex_.Unlock();
if (s.ok()) {
*dbptr = impl;
} else {
delete impl;
}
return s;
}
其实这也就讲了DB::Open的实现了你没发现吗?
然后就是回到最主要的问题——如何创建新的数据库?DB::Open里面有如下代码:
DBImpl* impl = new DBImpl(options, dbname);
//DBImpl在构造时会初始化互斥体与信号量,创建一个空的memtable,并根据配置设置
//Comparator及LRU缓冲
DBImpl::DBImpl(const Options& raw_options, const std::string& dbname)
: env_(raw_options.env),
internal_comparator_(raw_options.comparator),// 初始化Comparator
internal_filter_policy_(raw_options.filter_policy),
// 检查参数是否合法
options_(SanitizeOptions(dbname, &internal_comparator_, &internal_filter_policy_, raw_options)),
// 是拥有自己info log,还是使用用户提供的
owns_info_log_(options_.info_log != raw_options.info_log),
// 是否拥有自己的block LRU cache,或者使用用户提供的
owns_cache_(options_.block_cache != raw_options.block_cache),
dbname_(dbname),// 数据库名称
db_lock_(NULL),
shutting_down_(NULL),
bg_cv_(&mutex_),// 用于与后台线程交互的条件信号
mem_(NULL),// 跳表初识为NULL
imm_(NULL),
logfile_(NULL),
logfile_number_(0),// log文件的序号
log_(NULL),
seed_(0),
tmp_batch_(new WriteBatch),//用于write
bg_compaction_scheduled_(false),// 当前是否有后台的compaction线程正在进行合并
manual_compaction_(NULL) {
has_imm_.Release_Store(NULL);
// 设置table LRU cache的Entry数目不能超过max_open_files-10
const int table_cache_size = options_.max_open_files - kNumNonTableCacheFiles;
//table_cache_是各个sst文件元数据(index块以及布隆块)的缓存
table_cache_ = new TableCache(dbname_, &options_, table_cache_size);
//leveldb一共使用了两种lru cache,它们的功能不同,一个是table_cache,一个是block_cache
// 创建一个Version管理器
versions_ = new VersionSet(dbname_, &options_, table_cache_, &internal_comparator_);
}
Options SanitizeOptions(const std::string& dbname, const InternalKeyComparator* icmp, const Options& src) {
Options result = src;
result.comparator = icmp;
ClipToRange(&result.max_open_files, 20, 50000);
ClipToRange(&result.write_buffer_size, 64<<10, 1<<30);
ClipToRange(&result.block_size, 1<<10, 4<<20);
// 如果用户未指定info log文件(用于打印状态等文本信息的日志文件),则由引擎自己创建一个info log文件。
if (result.info_log == NULL) {
// Open a log file in the same directory as the db
src.env->CreateDir(dbname); // 如果目录不存在则创建
// 如果已存在以前的info log文件,则将其改名为LOG.old,然后创建新的log文件与日志的writer
src.env->RenameFile(InfoLogFileName(dbname), OldInfoLogFileName(dbname));
Status s = src.env->NewLogger(InfoLogFileName(dbname), &result.info_log);
if (!s.ok()) {
result.info_log = NULL;
}
}
// 如果用户没指定block_cache LRU缓冲,则创建8MB的LRU缓冲
if (result.block_cache == NULL) {
result.block_cache = NewLRUCache(8 << 20);
}
return result;
}
然后还有一句话在DB::Open里面:Status s = impl->Recover(&edit, &save_manifest);
会调用Recover(讲解在https://blog.csdn.net/weixin_36145588/article/details/78029415)Recover里面会去调用s = NewDB();
Status DBImpl::NewDB() {
// 创建version管理器
VersionEdit new_db;
// 设置Comparator
new_db.SetComparatorName(user_comparator()->Name());
new_db.SetLogNumber(0);
// 下一个序号从2开始,1留给清单文件
new_db.SetNextFile(2);
new_db.SetLastSequence(0);
// 创建一个清单文件,MANIFEST-1
const std::string manifest = DescriptorFileName(dbname_, 1);
WritableFile* file;
Status s = env_->NewWritableFile(manifest, &file);
if (!s.ok()) {
return s;
}
{
// 写入清单文件头
log::Writer log(file);
std::string record;
new_db.EncodeTo(&record);
s = log.AddRecord(record);
if (s.ok()) {
s = file->Close();
}
}
delete file;
if (s.ok()) {
// 设置CURRENT文件,使其指向清单文件
s = SetCurrentFile(env_, dbname_, 1);
} else {
env_->DeleteFile(manifest);
}
return s;
}
这就是大致的流程
创建了什么文件,都有什么作用?
主要看这里:https://blog.csdn.net/MOU_IT/article/details/117163155
2.DBImpl类内都有哪些成员?其作用是什么?¶
主要看这里(https://github.com/balloonwj/CppGuide/blob/master/articles/leveldb%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/leveldb%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%9015.md)
有必要先了解下DBImpl这个具体的实现类。主要是它的成员变量,这说明了它都利用了哪些组件。 整篇代码里面,这算是一个庞然大物了。现在只是先打第一个照面吧,后面的路还很长,先来看看类成员。
//== 第一组,他们在构造函数中初始化后将不再改变。其中,InternalKeyComparator和InternalFilterPolicy已经分别在Memtable和FilterPolicy中分析过。
Env* const env_; // 环境,封装了系统相关的文件操作、线程等等
const InternalKeyComparatorinternal_comparator_; // key comparator
const InternalFilterPolicyinternal_filter_policy_; // filter policy
const Options options_; //options_.comparator == &internal_comparator_
bool owns_info_log_;
bool owns_cache_;
const std::string dbname_;
//== 第二组,只有两个。
TableCache* table_cache_; // Table cache,线程安全的
FileLock* db_lock_;// 锁db文件,persistent state,直到leveldb进程结束
//== 第三组,被mutex_包含的状态和成员
port::Mutex mutex_; // 互斥锁
port::AtomicPointershutting_down_;
port::CondVar bg_cv_; // 在background work结束时激发
MemTable* mem_;
MemTable* imm_; // Memtablebeing compacted
port::AtomicPointerhas_imm_; // BGthread 用来检查是否是非NULL的imm_
// 这三个是log相关的
WritableFile* logfile_; // log文件
uint64_t logfile_number_; // log文件编号
log::Writer* log_; // log writer
//== 第四组,没有规律
std::deque<Writer*>writers_; // writers队列.
WriteBatch* tmp_batch_;
SnapshotList snapshots_; //snapshot列表
// Setof table files to protect from deletion because they are
// part ofongoing compactions.
std::set<uint64_t>pending_outputs_; // 待copact的文件列表,保护以防误删
bool bg_compaction_scheduled_; // 是否有后台compaction在调度或者运行?
Status bg_error_; // paranoid mode下是否有后台错误?
ManualCompaction*manual_compaction_; // 手动compaction信息
CompactionStatsstats_[config::kNumLevels]; // compaction状态
VersionSet* versions_; // 多版本DB文件,又一个庞然大物
还缺一个详细的阐释
3.DB::Open函数的实现¶
之前有所提到过
4.DBImpl::Recover函数的实现¶
我觉得写的最好的在这里:
函数声明为:
StatusDBImpl::Recover(VersionEdit* edit)
如果调用成功则设置VersionEdit。Recover的基本功能是:首先是处理创建flag,比如存在就返回失败等等;然后是尝试从已存在的sstable文件恢复db;最后如果发现有大于原信息记录的log编号的log文件,则需要回放log,更新db数据。回放期间db可能会dump新的level 0文件,因此需要把db元信息的变动记录到edit中返回。函数逻辑如下:
S1 创建目录,目录以db name命名,忽略任何创建错误,然后尝试获取db name/LOCK文件锁,失败则返回。
env_->CreateDir(dbname_);
Status s = env_->LockFile(LockFileName(dbname_), &db_lock_);
if (!s.ok()) return s;
S2 根据CURRENT文件是否存在,以及option参数执行检查。 如果文件不存在&create_is_missing=true,则调用函数NewDB()创建;否则报错。 如果文件存在& error_if_exists=true,则报错。 S3 调用VersionSet的Recover()函数,就是从文件中恢复数据。如果出错则打开失败,成功则向下执行S4。
s = versions_->Recover();
S4尝试从所有比manifest文件中记录的log要新的log文件中恢复(前一个版本可能会添加新的log文件,却没有记录在manifest中)。另外,函数PrevLogNumber()已经不再用了,仅为了兼容老版本。
// S4.1 这里先找出所有满足条件的log文件:比manifest文件记录的log编号更新。
SequenceNumber max_sequence(0);
const uint64_t min_log = versions_->LogNumber();
const uint64_t prev_log = versions_->PrevLogNumber();
std::vector<std::string>filenames;
s = env_->GetChildren(dbname_, &filenames); // 列出目录内的所有文件
uint64_t number;
FileType type;
std::vector<uint64_t>logs;
for (size_t i = 0; i < filenames.size(); i++) { // 检查log文件是否比min log更新
if (ParseFileName(filenames[i], &number, &type) && type == kLogFile
&& ((number >= min_log) || (number == prev_log))) {
logs.push_back(number);
}
}
// S4.2 找到log文件后,首先排序,保证按照生成顺序,依次回放log。并把DB元信息的变动(sstable文件的变动)追加到edit中返回。
std::sort(logs.begin(), logs.end());
for (size_t i = 0; i < logs.size(); i++) {
s = RecoverLogFile(logs[i], edit, &max_sequence);
// 前一版可能在生成该log编号后没有记录在MANIFEST中,
//所以这里我们手动更新VersionSet中的文件编号计数器
versions_->MarkFileNumberUsed(logs[i]);
}
// S4.3 更新VersionSet的sequence
if (s.ok()) {
if (versions_->LastSequence() < max_sequence)
versions_->SetLastSequence(max_sequence);
}
上面就是Recover的执行流程。
DBImpl::Recover做了以下事情:
- 创建数据库目录;
- 对这个数据库里面的LOCK文件加文件锁,LevelDB是单进程多线程的,需要保证每次只有一个进程能够打开数据库,方式就是使用了文件锁,如果有其它进程打开了数据库,那么加锁就会失败;
- 如果数据库不存在,那么调用
DBImpl::NewDB创建新的数据库; - 调用
VersionSet::Recover来读取MANIFEST,恢复版本信息; - 根据版本信息,搜索数据库目录,找到关闭时没有写入到SSTable的日志,按日志写入顺序逐个恢复日志数据。
DBImpl::RecoverLogFile会创建一个MemTable,开始读取日志信息,将日志的数据插入到MemTable,并根据需要调用DBImpl::WriteLevel0Table将MemTable写入到SSTable中
DBImpl::Recover做了以下事情
- 创建数据库目录;
- 对这个数据库里面的LOCK文件加文件锁,单进程的
- 如果数据库不存在,那么调用DBImpl::NewDB创建新的数据库;
- 调用VersionSet::Recover来读取MANIFEST,恢复版本信息;
- 根据版本信息,搜索数据库目录,找到关闭时没有写入到SSTable的日志,按日志写入顺序逐个恢复日志数据
- DBImpl::RecoverLogFile会创建一个MemTable,开始读取日志信息,将日志的数据插入到MemTable
- 并根据需要调用DBImpl::WriteLevel0Table将MemTable写入到SSTable中
LevelDB包含多种不同的数据文件,包括日志文件,manifest管理文件,数据文件等等。Recover函数的流程分为三个部分,第一部分检测数据库是存在,如果数据库实例是第一次创建,需要创建这些文件,并进行必要的初始化。否则,将读入这些文件,在内存中依据这些文件创建DB实例,第二部分是根据对多版本并发控制的需要生成对应的版本管理对象 VersionSet,Version;第三部分的代码主要负责检测是否有已经写入但是尚未执行的log日志存在,对这些存在遗漏的log日志进行处理。
1、首先检查是否是新打开的DB,如果是,则从调用NewDB创建新的DB 2、如果是重启DB,那么先根据Manifest文件,读取Manifest中每次版本的更改,恢复出当前的版本 3、从WAL中恢复memtable
Status DBImpl::Recover(VersionEdit* edit, bool* save_manifest) {
...
if (!env_->FileExists(CurrentFileName(dbname_))) {
if (options_.create_if_missing) {
Log(options_.info_log, "Creating DB %s since it was missing.",
dbname_.c_str());
// 1、原DB不存在,则是新打开的DB
s = NewDB();
if (!s.ok()) {
return s;
}
}
...
// 2、如果是重启DB,那么先根据Manifest文件,读取Manifest中每次版本的更改,恢复出当前的版本
s = versions_->Recover(save_manifest);
...
// 3.从WAL中恢复memtable
std::sort(logs.begin(), logs.end());
for (size_t i = 0; i < logs.size(); i++) {
s = RecoverLogFile(logs[i], (i == logs.size() - 1), save_manifest, edit,
&max_sequence);
if (!s.ok()) {
return s;
}
// The previous incarnation may not have written any MANIFEST
// records after allocating this log number. So we manually
// update the file number allocation counter in VersionSet.
versions_->MarkFileNumberUsed(logs[i]);
}
return Status::OK();
}
5.DBImpl::NewDB函数的实现¶
DBImpl::NewDB出人意料的简单,一个新的数据库没有任何数据,所以不需要日志和SSTable,只需要有一个MANIFEST文件,包含一些元数据。
可以看到DBImpl::NewDB非常简单,就是创建一个MANIFEST文件,将以下信息写入到MANIFEST文件:
比较器名称;
当前日志的编号;
下一个使用的文件编号;
上一个使用的SequenceNumber;
最后CURRENT指向新创建的MANIFEST文件。
当外部在调用DB::Open()时设置了option指定如果db不存在就创建,如果db不存在leveldb就会调用函数创建新的db。判断db是否存在的依据是/CURRENT文件是否存在。其逻辑很简单。
// S1首先生产DB元信息,设置comparator名,以及log文件编号、文件编号,以及seq no。
VersionEdit new_db;
new_db.SetComparatorName(user_comparator()->Name());
new_db.SetLogNumber(0);
new_db.SetNextFile(2);
new_db.SetLastSequence(0);
// S2 生产MANIFEST文件,将db元信息写入MANIFEST文件。
const std::string manifest = DescriptorFileName(dbname_, 1);
WritableFile* file;
Status s = env_->NewWritableFile(manifest, &file);
if (!s.ok()) return s;
{
log::Writer log(file);
std::string record;
new_db.EncodeTo(&record);
s = log.AddRecord(record);
if (s.ok()) s = file->Close();
}
delete file;
// S3 如果成功,就把MANIFEST文件名写入到CURRENT文件中
if (s.ok()) s = SetCurrentFile(env_, dbname_, 1);
else env_->DeleteFile(manifest);
return s;
还有一则:
Status DBImpl::NewDB() {
// 创建version管理器
VersionEdit new_db;
// 设置Comparator
new_db.SetComparatorName(user_comparator()->Name());
new_db.SetLogNumber(0);
// 下一个序号从2开始,1留给清单文件
new_db.SetNextFile(2);
new_db.SetLastSequence(0);
// 创建一个清单文件,MANIFEST-1
const std::string manifest = DescriptorFileName(dbname_, 1);
WritableFile* file;
Status s = env_->NewWritableFile(manifest, &file);
if (!s.ok()) {
return s;
}
{
// 写入清单文件头
log::Writer log(file);
std::string record;
new_db.EncodeTo(&record);
s = log.AddRecord(record);
if (s.ok()) {
s = file->Close();
}
}
delete file;
if (s.ok()) {
// 设置CURRENT文件,使其指向清单文件
s = SetCurrentFile(env_, dbname_, 1);
} else {
env_->DeleteFile(manifest);
}
return s;
}
6.VersionSet::Recover函数的实现。CURRENT的作用是什么?¶
VersionSet::Recover完成MANIFEST文件的读取和版本的构造,需要知道数据库里有哪些SSTable文件,每个文件处于哪个Level,当前日志的编号等等信息。
上面代码省略了如何读取MANIFEST文件,我们知道MANIFEST使用和WAL相同的格式,并且之前的版本变更介绍了Builder里应用多个VersionEdit,所以这里不过多介绍。恢复出版本信息后,安装这个版本,那么数据库的元数据就恢复到了关闭时候的状态,这个数据库就准备好了可以读写了。
VersionSet::Recover会通过读取 CURRENT 文件,找到当前正在使用的 MANIFEST 文件,然后读取 MANIFEST 文件,将数据库恢复到上次关闭前的状态。
如果 MANIFEST 文件大小超过阈值,无法继续使用了,save_manifest会被设为true。表示当前的 MANIFEST 文件需要被保存。
// 从数据库中读取 CURRENT 文件,解析 MANIFEST文件,
// 恢复数据库的状态(每层 Level 都有哪些 SST 文件)。
Status VersionSet::Recover(bool* save_manifest) {
struct LogReporter : public log::Reader::Reporter {
Status* status;
void Corruption(size_t bytes, const Status& s) override {
if (this->status->ok()) *this->status = s;
}
};
// 读取 CURRENT 文件的内容,存放到 std::string current 中。
std::string current;
Status s = ReadFileToString(env_, CurrentFileName(dbname_), ¤t);
if (!s.ok()) {
return s;
}
// 如果 CURRENT 文件为空,或者没有以 '\n' 结尾,说明 CURRENT 文件有问题,
// 返回失败。
if (current.empty() || current[current.size() - 1] != '\n') {
return Status::Corruption("CURRENT file does not end with newline");
}
// 去掉 current 中的 '\n'。
current.resize(current.size() - 1);
// current 中存放的是 MANIFEST 的文件名,例如 MANIFEST-000001。
// dscname 为 MANIFEST 的完整路径。
std::string dscname = dbname_ + "/" + current;
SequentialFile* file;
// 以顺序读取的方式打开 MANIFEST 文件。
s = env_->NewSequentialFile(dscname, &file);
if (!s.ok()) {
if (s.IsNotFound()) {
return Status::Corruption("CURRENT points to a non-existent file", s.ToString());
}
return s;
}
bool have_log_number = false;
bool have_prev_log_number = false;
bool have_next_file = false;
bool have_last_sequence = false;
uint64_t next_file = 0;
uint64_t last_sequence = 0;
uint64_t log_number = 0;
uint64_t prev_log_number = 0;
Builder builder(this, current_);
int read_records = 0;
{
// 构造一个 reader,用于读取 MANIFEST 文件。
LogReporter reporter;
reporter.status = &s;
log::Reader reader(file, &reporter, true /*checksum*/, 0 /*initial_offset*/);
Slice record;
std::string scratch;
// 读取 MANIFEST 文件中的每一条 Record,
// 把 Record 解码成一个 VersionEdit,
// 然后调用 builder.Apply(&edit) 将 edit 应用到 builder 中。
while (reader.ReadRecord(&record, &scratch) && s.ok()) {
++read_records;
// 将 record 解码成 VersionEdit。
VersionEdit edit;
s = edit.DecodeFrom(record);
if (s.ok()) {
// 检查解码出来的 VersionEdit.comparator 与当前数据库的 comparator 是否一致。
if (edit.has_comparator_ && edit.comparator_ != icmp_.user_comparator()->Name()) {
s = Status::InvalidArgument(
edit.comparator_ + " does not match existing comparator ",
icmp_.user_comparator()->Name());
}
}
// 将 VersionEdit 应用到 builder 中。
if (s.ok()) {
builder.Apply(&edit);
}
// 记录最新状态下的 WAL 编号。
if (edit.has_log_number_) {
log_number = edit.log_number_;
have_log_number = true;
}
// 记录最新状态下的 prev WAL 编号。
if (edit.has_prev_log_number_) {
prev_log_number = edit.prev_log_number_;
have_prev_log_number = true;
}
// 记录最新状态下的 next_file 编号。
if (edit.has_next_file_number_) {
next_file = edit.next_file_number_;
have_next_file = true;
}
// 记录最新状态下的 last_sequence 编号。
if (edit.has_last_sequence_) {
last_sequence = edit.last_sequence_;
have_last_sequence = true;
}
}
}
// 读取 MANIFEST 文件完毕,关闭文件。
delete file;
file = nullptr;
if (s.ok()) {
if (!have_next_file) {
s = Status::Corruption("no meta-nextfile entry in descriptor");
} else if (!have_log_number) {
s = Status::Corruption("no meta-lognumber entry in descriptor");
} else if (!have_last_sequence) {
s = Status::Corruption("no last-sequence-number entry in descriptor");
}
if (!have_prev_log_number) {
prev_log_number = 0;
}
// 标记 next_file_number_ 和 log_number 都已经被占用了。
MarkFileNumberUsed(prev_log_number);
MarkFileNumberUsed(log_number);
}
if (s.ok()) {
// 基于 MANIFEST 里的 VersionEdit列表,构造一个新的 Version。
Version* v = new Version(this);
builder.SaveTo(v);
// 把 New Version 作为当前 Version。
Finalize(v);
AppendVersion(v);
manifest_file_number_ = next_file;
next_file_number_ = next_file + 1;
last_sequence_ = last_sequence;
log_number_ = log_number;
prev_log_number_ = prev_log_number;
// 检查是否可以继续使用当前的 MANIFEST 文件。
if (ReuseManifest(dscname, current)) {
// No need to save new manifest
} else {
// 如果不能继续使用的话,需要把当前的 MANIFEST 进行保存。
*save_manifest = true;
}
} else {
std::string error = s.ToString();
Log(options_->info_log, "Error recovering version set with %d records: %s", read_records,
error.c_str());
}
return s;
}
主要看这里:https://github.com/balloonwj/CppGuide/blob/master/articles/leveldb%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/leveldb%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%9018.md
还有这里:https://blog.csdn.net/sinat_38293503/article/details/135661973#VersionSetRecoverbool_save_manifest_432
CURRENT文件作用的说法1
随着更新与Compaction的进行,LevelDB会不断生成新文件,有时还会删除老文件,所以需要一个文件来记录文件列表,这个列表就是清单文件(manifest文件)的作用,清单会不断变化,DB需要知道最新的清单文件,必须将清单准备好后原子切换,这就是CURRENT文件的作用,LevelDB的清单过程更新如下:
- 递增清单序号,生成一个新的清单文件。
- 将此清单文件的名称写入到一个临时文件中。
- 将临时文件rename为CURRENT。
CURRENT 文件中存储的是当前正在使用的 MANIFEST 文件。
当创建新的 MANIFEST 文件时,LevelDB 会先更新 CURRENT文件,使其指向新的 MANIFEST 文件,然后再将旧的 MANIFEST 文件删除。
倘若没有 CURRENT 文件,新 MANIFEST 文件创建后,还没来得及删除旧的 MANIFEST 文件,系统就崩溃了,那么 LevelDB 恢复时就不知道当前正在使用的 MANIFEST 文件是哪个,也就无法恢复到正确的状态。