leveldb深度剖析-初始化流程

本篇来介绍一下leveldb初始化流程,初始化流程中涉及了很多内容,例如:文件组织方式,存储内容序列化和反序列化等等。在了解这些内容后,是有助于日后分析存储流程和压缩流程的。

一、创建/Open数据库

leveldb属于轻量级kv数据库,可以很方便写一个hello程序并结合gdb单步调试,来辅助我们阅读源码,起到事半功倍的效果。

1.1、版本管理

leveldb采用分层思想对数据进行管理,那么就需要一个对象来管理每层数据元信息,这个对象就是VersionSet,在源码中和Version有关的对象一共有三个分别为:

对象作用
VersionSetVersion集合,所有的Version都挂在VersionSet对象下面,一个db只有一个VersionSet
Version一个db可能存在多个Version,Version之间采用链表方式管理,链表最后节点为当前最新Version信息
VersionEdit该对象用于生成最新的Version。VersionEdit + 当前使用的Version 生成 新的Version存放到链表尾部,只在需要生成新的Version时才会创建

1.2、Open函数

调用接口Open,用于创建数据库(如果数据不存在),下面是Open函数流程图:

Status DB::Open(const Options& options, const std::string& dbname,
                DB** dbptr) {
  *dbptr = NULL;

  DBImpl* impl = new DBImpl(options, dbname);//初始化DBImpl对象
  impl->mutex_.Lock();
  VersionEdit edit;
  // Recover handles create_if_missing, error_if_exists 恢复数据
  bool save_manifest = false;
  Status s = impl->Recover(&edit, &save_manifest);//恢复数据
  
  if (s.ok() && impl->mem_ == NULL) {//创建memTable对象
    // Create new log and a corresponding memtable.
    uint64_t new_log_number = impl->versions_->NewFileNumber();
    // 创建*.log文件
    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为true表示需要重新创建一个MANIFEST文件
    edit.SetPrevLogNumber(0);  // No older logs needed after recovery.
    edit.SetLogNumber(impl->logfile_number_);
    s = impl->versions_->LogAndApply(&edit, &impl->mutex_);//由于log相关数据变化 所以要写回Version信息
  }
  
  if (s.ok()) {
    impl->DeleteObsoleteFiles();//删除旧版本文件
    impl->MaybeScheduleCompaction();//启动压缩调度流程 单独启动一个线程
  }
  
  impl->mutex_.Unlock();
  
  if (s.ok()) {
    assert(impl->mem_ != NULL);
    *dbptr = impl;
  } else {
    delete impl;
  }
  return s;
}

1.2.1、Recover数据恢复

DBImpl的构造函数实现并不复杂,不再展开说明。这里详细说明一下Recover的实现,Recover只做了两件主要事情:恢复Version信息和恢复MemTable(读取.log文件),具体流程图如下:

/**
 * 数据库环境恢复
 * @param edit 
 * @param save_manifest 
 */
Status DBImpl::Recover(VersionEdit* edit, bool *save_manifest) {
  mutex_.AssertHeld();

  // Ignore error from CreateDir since the creation of the DB is
  // committed only when the descriptor is created, and this directory
  // may already exist from a previous failed creation attempt.
  env_->CreateDir(dbname_);
  assert(db_lock_ == NULL);
  // 创建文件锁 支持多进程并发访问同一个数据库 env_指向env_posix.cc
  Status s = env_->LockFile(LockFileName(dbname_), &db_lock_);
  if (!s.ok()) {
    return s;
  }
  // 判断CURRENT文件是否存在
  if (!env_->FileExists(CurrentFileName(dbname_))) {
    if (options_.create_if_missing) {
      s = NewDB(); //创建数据库,主要包含CURRENT、MANIFEST文件,不包含*.log文件
      if (!s.ok()) {
        return s;
      }
    } else {
      return Status::InvalidArgument(
          dbname_, "does not exist (create_if_missing is false)");
    }
  } else {
    if (options_.error_if_exists) {
      return Status::InvalidArgument(
          dbname_, "exists (error_if_exists is true)");
    }
  }
  /**
   * 以上是数据文件校验 如果不存在则创建新的数据库文件  以下内容是从数据库文件 
   * 中恢复数据,如版本信息
   */
  s = versions_->Recover(save_manifest);//version_set.cc 
  if (!s.ok()) {
    return s;
  }
  SequenceNumber max_sequence(0);

  // Recover from all newer log files than the ones named in the
  // descriptor (new log files may have been added by the previous
  // incarnation without registering them in the descriptor).
  //
  // Note that PrevLogNumber() is no longer used, but we pay
  // attention to it in case we are recovering a database
  // produced by an older version of leveldb.
  const uint64_t min_log = versions_->LogNumber();
  const uint64_t prev_log = versions_->PrevLogNumber();
  //获取目录下所有目录或文件名字
  std::vector<std::string> filenames;
  s = env_->GetChildren(dbname_, &filenames);
  if (!s.ok()) {
    return s;
  }
  std::set<uint64_t> expected;
  versions_->AddLiveFiles(&expected);
  uint64_t number;
  FileType type;
  std::vector<uint64_t> logs;
  for (size_t i = 0; i < filenames.size(); i++) {
    if (ParseFileName(filenames[i], &number, &type)) {
      expected.erase(number);
      if (type == kLogFile && ((number >= min_log) || (number == prev_log)))
        logs.push_back(number);
    }
  }
  if (!expected.empty()) {//不空表示有文件丢失 出错
    char buf[50];
    snprintf(buf, sizeof(buf), "%d missing files; e.g.",
             static_cast<int>(expected.size()));
    return Status::Corruption(buf, TableFileName(dbname_, *(expected.begin())));
  }

  // Recover in the order in which the logs were generated
  // 按从小到大顺序读取.log文件 生成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]);
  }

  if (versions_->LastSequence() < max_sequence) {
    versions_->SetLastSequence(max_sequence);//保存最大记录号
  }

  return Status::OK();
}

这里需要提示一下:.log文件相当于数据库操作日志,.log中每条记录都与MemTable中记录一一对应.遍历完所有.log文件MemTable就生成了。

这里简单说明一下数据插入流程:leveldb首先将一条记录插入到.log文件,然后在插入到MemTable中。当leveldb数据库重启时,需要读取每个.log文件以便恢复MemTable,这个恢复流程就是在RecoverLoFile中实现的。

1.2.2、VersionEdit恢复

在之前的博客介绍了MANIFEST存储的内容为VersionEdit,该对象包含了很多内容,例如:log文件编号,压缩点,待删文件序号以及保存的最小key和最大key。该函数主要是针对MANIFEST文件的解析

/**
 * 恢复数据 从MANIFEST文件中读取出VersionEdit来初始化VersionSet
 * @param save_manifest  输出参数 
 *          true  -  需要创建新的manifest文件
 *          false -  不需要创建新的manifest文件
 * @return 返回操作状态
 */
Status VersionSet::Recover(bool *save_manifest) {
  struct LogReporter : public log::Reader::Reporter {
    Status* status;
    virtual void Corruption(size_t bytes, const Status& s) {
      if (this->status->ok()) *this->status = s;
    }
  };

  // Read "CURRENT" file, which contains a pointer to the current manifest file  
  std::string current;
  Status s = ReadFileToString(env_, CurrentFileName(dbname_), &current);
  if (!s.ok()) {
    return s;
  }
  if (current.empty() || current[current.size()-1] != '\n') {
    return Status::Corruption("CURRENT file does not end with newline");
  }
  current.resize(current.size() - 1);

  //读取MANIFEST文件
  std::string dscname = dbname_ + "/" + current;
  SequentialFile* file;
  s = env_->NewSequentialFile(dscname, &file);//env_posix.cc
  if (!s.ok()) {
    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;
  // current_ 保存的当前最新的Version信息 后面会调用Builder saveto方法与current_
  // 指定的Version信息进行合并
  Builder builder(this, current_); //Builder类定义在version_set.cc

  {
    LogReporter reporter;
    reporter.status = &s;
    //创建读取执行器 读取MANIFEST文件
    log::Reader reader(file, &reporter, true/*checksum*/, 0/*initial_offset*/);
    Slice record;
    std::string scratch;//在ReadRecord内部使用
    // 读取MAINIFEST文件 逐条读取反序列化VersionEdit对象
    while (reader.ReadRecord(&record, &scratch) && s.ok()) {//log_reader.cc
      VersionEdit edit;
      s = edit.DecodeFrom(record);//解码 到VersionEdit
      if (s.ok()) {
        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对象中最有用的内容就是: 文件信息以及压缩点,所以需要对其进行整合
      if (s.ok()) {
        builder.Apply(&edit);
      }

      if (edit.has_log_number_) {
        log_number = edit.log_number_;
        have_log_number = true;
      }

      if (edit.has_prev_log_number_) {
        prev_log_number = edit.prev_log_number_;
        have_prev_log_number = true;
      }

      if (edit.has_next_file_number_) {
        next_file = edit.next_file_number_;
        have_next_file = true;
      }

      if (edit.has_last_sequence_) {
        last_sequence = edit.last_sequence_;
        have_last_sequence = true;
      }
    }
  }
  delete file;
  file = NULL;

  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;
    }

    MarkFileNumberUsed(prev_log_number);
    MarkFileNumberUsed(log_number);
  }

  if (s.ok()) {
    //创建新的Version 与旧的Version进行合并,并保存到新的Version中
    Version* v = new Version(this);
    builder.SaveTo(v);
    
    // Install recovered version
    Finalize(v);//预计算压缩层次以及分数
    AppendVersion(v);// 将version插入到Version双向链表尾部
    // 设置VersionSet中的序号信息
    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;

    // See if we can reuse the existing MANIFEST file.
    if (ReuseManifest(dscname, current)) {
      // No need to save new manifest
    } else {
      *save_manifest = true;
    }
  }

  return s;
}

二、总结

本篇了主要介绍leveldb初始化流程,只有了解数据库组织方式,才有助于我们后续分析存储流程,如果对于leveldb存储刘结构还不清楚的,可参考本篇《leveldb深度剖析-存储结构(1)》,下一篇将介绍存储流程

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值