leveldb源码分析:Open启动流程

leveldb概述

Leveldb 是一个持久化的KV存储系统,主要将大部分数据存储在磁盘上,在存储数据的过程中,根据记录的key值有序存储,当然使用者也可以自定义Key大小比较函数,一个leveldb数据库类似与一个操作系统文件夹,所有的有关数据库内容都存储在该文件夹下面,并提供Put,Delete和Get等方法去更改或查询该数据库,并且提供原子更新,同步写(因为leveldb默认情况下是异步写,主要是通过操作系统的fsync等系统调用来实现的),支持数据快照,使得读取操作不受写操作影响并在读过程中始终看到一致的数据等特性。根据官网数据,leveldb的随机写性能大大快于读操作,适合写多读少的场景。后续特性待具体分析时具体分析。由于leveldb是由C++编写的,大家需要熟悉一下C++的基础知识。

leveldb的示例代码

本文示例代码如下:

#include "leveldb/db.h"
#include <cassert>
#include <iostream>

using namespace std;
using namespace leveldb;

int main(){
	leveldb::DB *db;
	leveldb::Options options;
	options.create_if_missing = true;
	leveldb::Status status = leveldb::DB::Open(options, "/root/testdb", &db);
	assert(status.ok());

	status = db->Put(WriteOptions(), "KeyNameExample", "ValueExample");
	assert(status.ok());
	string res;
	status = db->Get(ReadOptions(), "KeyNameExample", &res);
	assert(status.ok());
	cout<< res << endl;
	delete db;
	return 0;
}

主要涉及到了Open,Put,Get等操作。查看具体的内容分析

leveldb打开启动流程

主要就是讲述DB::Open的相关操作流程。

open函数的流程

按照示例代码可知导入的模块的内容是leveldb/db.h中的类DB,大家可自行查看该类,该类是一个基类,需要继承并实现相关的方法,此时的Open是一个类的Static方法,实现的具体方法位于db/db_impl.cc文件中;

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

  DBImpl* impl = new DBImpl(options, dbname);                           // 新生产一个DBImpl类并赋值给dbptr指针
  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_ == nullptr) {                                // 如果mem_ 指向的就是memtable 就是内存保存的数据库的地址
    // Create new log and a corresponding memtable. 
    uint64_t new_log_number = impl->versions_->NewFileNumber();         //    新生成一个版本号
    WritableFile* lfile;
    s = options.env->NewWritableFile(LogFileName(dbname, new_log_number),       // 生产一个新的写文件
                                     &lfile);
    if (s.ok()) {                                                       // 如果创建成功则 先设置日志版本号
      edit.SetLogNumber(new_log_number);
      impl->logfile_ = lfile;                                           // 初始化impl 相关内容
      impl->logfile_number_ = new_log_number;
      impl->log_ = new log::Writer(lfile);                              // 初始化 日志
      impl->mem_ = new MemTable(impl->internal_comparator_);            // 重新生产一个MemTable保存数据
      impl->mem_->Ref();
    }
  }
  if (s.ok() && save_manifest) {                                        // 是否从旧版本的文件中恢复
    edit.SetPrevLogNumber(0);  // No older logs needed after recovery.
    edit.SetLogNumber(impl->logfile_number_);
    s = impl->versions_->LogAndApply(&edit, &impl->mutex_);             
  }
  if (s.ok()) {
    impl->DeleteObsoleteFiles();                                      // 删除所有不是当前日志文件的日志文件 删除所有未从某个级别引用的表文件
    impl->MaybeScheduleCompaction();                                  // 是否需要压缩合并
  }
  impl->mutex_.Unlock();                                              // 释放锁
  if (s.ok()) {
    assert(impl->mem_ != nullptr);                                    // 判断mem_不为空
    *dbptr = impl;                                                    // 赋值
  } else {
    delete impl;                                                      // 如果出错释放新生成的impl
  }
  return s;                                                           // 返回状态s
}

其中DBImpl就是实现了基类DB的虚方法,并实现了对应的功能,此时会先检查对应的意见存在的版本信息,如果已经存在则尝试是否需要从旧版本中恢复数据,然后如果不需要恢复则表示都是新创建的则进行数据的初始化,然后在设置日志文件的配置参数,最后再调用清理函数和检查是否需要合并的函数。

impl->Recover恢复函数
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_);                                                 // env_是根据不同平台获取得到的相关函数操作 是在DBImpl初始化时根据传入参数创建
  assert(db_lock_ == nullptr);
  Status s = env_->LockFile(LockFileName(dbname_), &db_lock_);              // 实现对文件的加锁, 如果其他文件以及锁住了该文件则返回锁是啊比
  if (!s.ok()) {
    return s;
  }

  if (!env_->FileExists(CurrentFileName(dbname_))) {                        // 检查CURRENT文件是否存在
    if (options_.create_if_missing) {                                       // 如果参数设置的是如果不存在则创建
      s = NewDB();                                                          // 生成一个DB 生成相关的文件
      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);                                //  根据save_manifest 来是否生成新的版本信息 根据读入的最后的MANIFEST存入的最后的数据
  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
  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();
}

主要通过文件锁,获取对数据库恢复的权限,如果有已经存在的数据库则读入当前存在的数据库相关信息,来判断是否需要重新建立0层level对应的数据,是否需要从日志文件中恢复数据,从日志恢复数据的操作主要是RecoverLogFile函数执行;

Status DBImpl::RecoverLogFile(uint64_t log_number, bool last_log,
                              bool* save_manifest, VersionEdit* edit,
                              SequenceNumber* max_sequence) {
  struct LogReporter : public log::Reader::Reporter {
    Env* env;
    Logger* info_log;
    const char* fname;
    Status* status;  // null if options_.paranoid_checks==false
    void Corruption(size_t bytes, const Status& s) override {
      Log(info_log, "%s%s: dropping %d bytes; %s",
          (this->status == nullptr ? "(ignoring error) " : ""), fname,
          static_cast<int>(bytes), s.ToString().c_str());
      if (this->status != nullptr && this->status->ok()) *this->status = s;
    }
  };

  mutex_.AssertHeld();                                            // 获取线程锁

  // Open the log file
  std::string fname = LogFileName(dbname_, log_number);           // 打开日志文件
  SequentialFile* file;
  Status status = env_->NewSequentialFile(fname, &file);          // 生成新的序列号文件
  if (!status.ok()) {
    MaybeIgnoreError(&status);
    return status;
  }

  // Create the log reader.
  LogReporter reporter;                                          // 生成一个reporter 并初始化参数
  reporter.env = env_;
  reporter.info_log = options_.info_log;
  reporter.fname = fname.c_str();
  reporter.status = (options_.paranoid_checks ? &status : nullptr);
  // We intentionally make log::Reader do checksumming even if
  // paranoid_checks==false so that corruptions cause entire commits
  // to be skipped instead of propagating bad information (like overly
  // large sequence numbers).
  log::Reader reader(file, &reporter, true /*checksum*/, 0 /*initial_offset*/);
  Log(options_.info_log, "Recovering log #%llu",
      (unsigned long long)log_number);

  // Read all the records and add to a memtable
  std::string scratch;
  Slice record;
  WriteBatch batch;
  int compactions = 0;
  MemTable* mem = nullptr;
  while (reader.ReadRecord(&record, &scratch) && status.ok()) {             // 读数据
    if (record.size() < 12) {
      reporter.Corruption(record.size(),
                          Status::Corruption("log record too small"));      // 打印信息
      continue;
    }
    WriteBatchInternal::SetContents(&batch, record);                        // 设置批量写入

    if (mem == nullptr) {
      mem = new MemTable(internal_comparator_);                             // 生成一个新的MemTable
      mem->Ref();
    }
    status = WriteBatchInternal::InsertInto(&batch, mem);                   // 插入信息
    MaybeIgnoreError(&status);
    if (!status.ok()) {
      break;
    }
    const SequenceNumber last_seq = WriteBatchInternal::Sequence(&batch) +
                                    WriteBatchInternal::Count(&batch) - 1;      // 统计计数
    if (last_seq > *max_sequence) {
      *max_sequence = last_seq;                                                 // 判断最后一个是否超过最大值
    }

    if (mem->ApproximateMemoryUsage() > options_.write_buffer_size) {         // 检查是否超过MemTable的阈值
      compactions++;
      *save_manifest = true;
      status = WriteLevel0Table(mem, edit, nullptr);                          // 写入0层level的数据
      mem->Unref();
      mem = nullptr;
      if (!status.ok()) {
        // Reflect errors immediately so that conditions like full
        // file-systems cause the DB::Open() to fail.
        break;
      }
    }
  }

  delete file;

  // See if we should keep reusing the last log file.
  if (status.ok() && options_.reuse_logs && last_log && compactions == 0) {     // 如果状态ok 重用log last_log有内容 compactions为0 
    assert(logfile_ == nullptr);
    assert(log_ == nullptr);
    assert(mem_ == nullptr);
    uint64_t lfile_size;
    if (env_->GetFileSize(fname, &lfile_size).ok() &&
        env_->NewAppendableFile(fname, &logfile_).ok()) {                      // 获取文件大小 获取文件名
      Log(options_.info_log, "Reusing old log %s \n", fname.c_str());
      log_ = new log::Writer(logfile_, lfile_size);                            // 创建一个写log
      logfile_number_ = log_number;
      if (mem != nullptr) {                                                    // 设置mem_
        mem_ = mem;
        mem = nullptr;
      } else {
        // mem can be nullptr if lognum exists but was empty.
        mem_ = new MemTable(internal_comparator_);
        mem_->Ref();
      }
    }
  }

  if (mem != nullptr) {                                                       // 如果mem 不为空 则创建0层level
    // mem did not get reused; compact it.
    if (status.ok()) {
      *save_manifest = true;
      status = WriteLevel0Table(mem, edit, nullptr);
    }
    mem->Unref();
  }

  return status;
}

至此,主要的leveldb的数据库打开操作就执行完成了。

总结

本文主要概述了leveldb的基本特性,并概述了leveldb启动Open函数的源码的执行流程,里面有关环境变量等类参数的初始化,需要大家在熟悉一下有关C++基础之后,再去查看相关的线程锁等实现细节。由于本人才疏学浅,如有错误请批评指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值