跳转至

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_), &current);
    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的清单过程更新如下:

  1. 递增清单序号,生成一个新的清单文件。
  2. 将此清单文件的名称写入到一个临时文件中。
  3. 将临时文件rename为CURRENT。

CURRENT 文件中存储的是当前正在使用的 MANIFEST 文件。

当创建新的 MANIFEST 文件时,LevelDB 会先更新 CURRENT文件,使其指向新的 MANIFEST 文件,然后再将旧的 MANIFEST 文件删除。

倘若没有 CURRENT 文件,新 MANIFEST 文件创建后,还没来得及删除旧的 MANIFEST 文件,系统就崩溃了,那么 LevelDB 恢复时就不知道当前正在使用的 MANIFEST 文件是哪个,也就无法恢复到正确的状态。