leveldb:VersionSet

VersionSet 是version组成一个双向循环链表。

class VersionSet{
//. . .
  Env* const env_;// 操作系统封装 
  const std::string dbname_;
  const Options* const options_;
  TableCache* const table_cache_;
  const InternalKeyComparator icmp_;
  uint64_t next_file_number_;
  uint64_t manifest_file_number_;
  uint64_t last_sequence_;
  uint64_t log_number_;

  WritableFile* descriptor_file_;//manifest文件的写描述符
  log::Writer* descriptor_log_;//manifest文件的日志包装形式
  Version dummy_versions_;  //versions双向链表head
  Version* current_;        // == dummy_versions_.prev_,当前最新的version

  //每层都有一个compact pointer用于指示下次从哪里开始compact,以用于实现循环compact
  std::string compact_pointer_[config::kNumLevels];
//. . .
}

VersionSet接口

构造函数

VersionSet::VersionSet(const std::string& dbname,
const Options* options,
TableCache* table_cache,
const InternalKeyComparator* cmp)
VersionSet的构造函数很简单,除了根据参数初始化,还有两个地方值得注意:

  1. next_file_number_从2开始;
  2. 创建新的Version并加入到Version链表中,并设置CURRENT=新创建version;
AppendVersion()

void VersionSet::AppendVersion(Version* v)
把v加入到versionset中的双向version链表头部,并设置为current version。并对老的current version执行Uref()。
这里写图片描述

LogAndApply()

Status VersionSet::LogAndApply(VersionEdit* edit, port::Mutex* mu)
功能:在currentversion上应用指定的VersionEdit,生成新的MANIFEST信息,保存到磁盘上,并用作current version,故为Log And Apply。
参数edit也会被函数修改。
这里写图片描述
相邻Version之间的不同仅仅是一些文件被删除另一些文件被删除。也就是说将文件变动应用在旧的Version上可以得到新的Version,这也就是Version产生的方式。LevelDB用VersionEdit来表示这种相邻Version的差值。
而LogAndApply()便是记录并应用这些差值的函数。

Status VersionSet::LogAndApply(VersionEdit* edit, port::Mutex* mu) {
  if (edit->has_log_number_) {
    assert(edit->log_number_ >= log_number_);
    assert(edit->log_number_ < next_file_number_);
  } else {
    edit->SetLogNumber(log_number_);
  }

  if (!edit->has_prev_log_number_) {
    edit->SetPrevLogNumber(prev_log_number_);
  }

  edit->SetNextFile(next_file_number_);
  edit->SetLastSequence(last_sequence_);
//创建一个新的Version v,并把新的edit变动保存到v中。Builder很重要,我们后面进行分析
  Version* v = new Version(this);
  {
  //这边是函数名字apply的含义,应用versionedit生成新版本v
    Builder builder(this, current_);
    builder.Apply(edit);
    builder.SaveTo(v);
  }
  Finalize(v);//为v计算执行compaction的最佳level

  std::string new_manifest_file;
  Status s;
  if (descriptor_log_ == NULL) {
    // 这里不需要unlock *mu因为我们只会在第一次调用(打开数据库时)LogAndApply时  
  // 才走到这里
    assert(descriptor_file_ == NULL);
    new_manifest_file = DescriptorFileName(dbname_, manifest_file_number_);
    edit->SetNextFile(next_file_number_);
    s = env_->NewWritableFile(new_manifest_file, &descriptor_file_);
    if (s.ok()) {
    //生成新manifest文件时必须得记录下当前数据库的快照,不清楚见VersionEdit与MANIFEST文件那一篇
      descriptor_log_ = new log::Writer(descriptor_file_);
      s = WriteSnapshot(descriptor_log_);
    }
  }

  // Unlock during expensive MANIFEST log write
  {
    mu->Unlock();

    // 把versionedit序列化并记入MANIFEST log,这边是函数名log的含义
    if (s.ok()) {
      std::string record;
      edit->EncodeTo(&record);
      s = descriptor_log_->AddRecord(record);
      if (s.ok()) {
        s = descriptor_file_->Sync();
      }
      if (!s.ok()) {
        Log(options_->info_log, "MANIFEST write: %s\n", s.ToString().c_str());
      }
    }

//如果刚才创建了一个MANIFEST文件,通过写一个指向它的CURRENT文件来记录最新的MANIFEST文件
    if (s.ok() && !new_manifest_file.empty()) {
      s = SetCurrentFile(env_, dbname_, manifest_file_number_);
    }

    mu->Lock();
  }

  // Install the new version
  if (s.ok()) {
    AppendVersion(v);// 安装这个version
    log_number_ = edit->log_number_;
    prev_log_number_ = edit->prev_log_number_;
  } else {
    delete v;
    if (!new_manifest_file.empty()) {
      delete descriptor_log_;
      delete descriptor_file_;
      descriptor_log_ = NULL;
      descriptor_file_ = NULL;
      env_->DeleteFile(new_manifest_file);
    }
  }

  return s;
}
VersionSet::Builder类

Builder是一个内部辅助类,其主要作用是:
1 把一个MANIFEST记录的versionedit应用到版本管理器VersionSet中;
2 把当前的版本状态设置到一个Version对象中。

成员与构造

Builder的vset_与base_都是调用者传入的,此外它还为FileMetaData定义了一个比较类BySmallestKey,首先依照文件的min key,小的在前;如果min key相等则file number小的在前,即越早创建的文件在前

  typedefstd::set<FileMetaData*, BySmallestKey> FileSet;  
  struct LevelState { // 这个是记录添加和删除的文件  
    std::set<uint64_t>deleted_files;  
    FileSet* added_files; // 保证添加文件的顺序是有效定义的  
  };  
  VersionSet* vset_;  
  Version* base_;  
  LevelStatelevels_[config::kNumLevels];  
// 其接口有3个:  
  void Apply(VersionEdit* edit)  
  void SaveTo(Version* v)  
  void MaybeAddFile(Version* v, int level, FileMetaData* f)  
Apply()

注意除了compaction点直接修改了vset_,其它删除和新加文件的变动只是先存储在Builder自己的成员变量中,在调用SaveTo(v)函数时才施加到v上。

  void Apply(VersionEdit* edit) {
    //把edit记录的compaction点应用到当前状态
    for (size_t i = 0; i < edit->compact_pointers_.size(); i++) {
      const int level = edit->compact_pointers_[i].first;
      vset_->compact_pointer_[level] =
          edit->compact_pointers_[i].second.Encode().ToString();
    }

    //  把edit记录的已删除文件记录到Builder
    const VersionEdit::DeletedFileSet& del = edit->deleted_files_;
    for (VersionEdit::DeletedFileSet::const_iterator iter = del.begin();
         iter != del.end();
         ++iter) {
      const int level = iter->first;
      const uint64_t number = iter->second;
      levels_[level].deleted_files.insert(number);
    }

    //把edit记录的新加文件记录到Builder,这里会初始化文件的allowed_seeks值,以在文件被无谓seek指定次数后自动执行compaction
    for (size_t i = 0; i < edit->new_files_.size(); i++) {
      const int level = edit->new_files_[i].first;
      FileMetaData* f = new FileMetaData(edit->new_files_[i].second);
      f->refs = 1;
//如何设置seeks次数呢?文件大小除以16k,不到100算100
      f->allowed_seeks = (f->file_size / 16384);
      if (f->allowed_seeks < 100) f->allowed_seeks = 100;

      levels_[level].deleted_files.erase(f->number);
      levels_[level].added_files->insert(f);
    }
  }

值allowed_seeks事关compaction的优化,其计算依据如下,首先假设:
1 一次seek时间为10ms
2 写入10MB数据的时间为10ms(100MB/s)
3 compact 1MB的数据需要执行25MB的IO
->从本层读取1MB
->从下一层读取10-12MB(文件的key range边界可能是非对齐的)
->向下一层写入10-12MB
这意味这25次seek的代价等同于compact 1MB的数据,也就是一次seek花费的时间大约相当于compact 40KB的数据。基于保守的角度考虑,对于每16KB的数据,我们允许它在触发compaction之前能做一次seek。

SaveTo()

把当前的状态存储到v中返回,函数声明:void SaveTo(Version* v)

 void SaveTo(Version* v) {
    BySmallestKey cmp;
    cmp.internal_comparator = &vset_->icmp_;
    for (int level = 0; level < config::kNumLevels; level++) {
      const std::vector<FileMetaData*>& base_files = base_->files_[level];
      std::vector<FileMetaData*>::const_iterator base_iter = base_files.begin();
      std::vector<FileMetaData*>::const_iterator base_end = base_files.end();
      const FileSet* added = levels_[level].added_files;
      //给v的level层预留空间,最多有base_的level层文件个数加上个level层新增的文件个数的文件
      v->files_[level].reserve(base_files.size() + added->size());
      //遍历第level层新增的文件个数
      for (FileSet::const_iterator added_iter = added->begin();
           added_iter != added->end();
           ++added_iter) {
        // //加入base_中小于added_iter的那些文件 
        for (std::vector<FileMetaData*>::const_iterator bpos
                 = std::upper_bound(base_iter, base_end, *added_iter, cmp);
             base_iter != bpos;
             ++base_iter) {
          MaybeAddFile(v, level, *base_iter);
        }

        MaybeAddFile(v, level, *added_iter);
      }

      // 添加base_剩余的那些文件 
      for (; base_iter != base_end; ++base_iter) {
        MaybeAddFile(v, level, *base_iter);
      }
}

我很好奇删除的文件呢?怎么没有见应用进base呢?
其实答案就在MaybeAddFile中

MaybeAddFile()

voidMaybeAddFile(Version* v, int level, FileMetaData* f)
函数声明:void MaybeAddFile(Version* v, int level, FileMetaData* f)
该函数尝试将f加入到v的第level层文件中。
要满足两个条件:
1 文件不能被删除,也就是不能在levels_[level].deleted_files集合中;
2 保证文件之间的key是连续的,即基于比较器vset_->icmp_,f的min key要大于levels_[level]集合中最后一个文件的max key;

  void MaybeAddFile(Version* v, int level, FileMetaData* f) {
    if (levels_[level].deleted_files.count(f->number) > 0) {
      // 如果新加入的文件在levels_[level].deleted_files集合中,就不会被加入v的第level层文件中。
      //原来version的每一个文件也都需要通过MaybeAddFile加入到新version v中,所以自然就删除了原来
      //version中没用的文件了
    } else {
      std::vector<FileMetaData*>* files = &v->files_[level];
      if (level > 0 && !files->empty()) {
        assert(vset_->icmp_.Compare((*files)[files->size()-1]->largest,
                                    f->smallest) < 0);
      }
      f->refs++;
      files->push_back(f);
    }
  }
Status VersionSet::Recover(bool *save_manifest)

对于VersionSet而言,Recover就是根据CURRENT指定的MANIFEST,读取db元信息。
对于该函数的具体分析,我们将放在leveldb的recover专题里分析。

void VersionSet::Finalize(Version* v)

该函数依照规则为下次的compaction计算出最适用的level,对于level 0和>0需要分别对待

void VersionSet::Finalize(Version* v) {
  // Precomputed best level for next compaction
  int best_level = -1;
  double best_score = -1;

  for (int level = 0; level < config::kNumLevels-1; level++) {
    double score;
    if (level == 0) {
   // 对于level 0以文件个数计算,kL0_CompactionTrigger默认配置为4,因为如果0层文件多
   //了会影响合并效率
      score = v->files_[level].size() /
          static_cast<double>(config::kL0_CompactionTrigger);
    } else {
      //对于level>0,根据level内的文件总大小计算
      //其中函数MaxBytesForLevel根据level返回其本层文件总大小的预定最大值。
    //计算规则为:1048576.0* level^10。
      const uint64_t level_bytes = TotalFileSize(v->files_[level]);
      score =
          static_cast<double>(level_bytes) / MaxBytesForLevel(options_, level);
    }

    if (score > best_score) {
      best_level = level;
      best_score = score;
    }
  }
//遍历了所有层后,将compaction_score值最大的level作为下次合并的level
  v->compaction_level_ = best_level;
  v->compaction_score_ = best_score;
}
Status VersionSet::WriteSnapshot(log::Writer* log)

把currentversion保存到*log中,信息包括comparator名字、compaction点和各级sstable文件

Status VersionSet::WriteSnapshot(log::Writer* log) {
  VersionEdit edit;
  edit.SetComparatorName(icmp_.user_comparator()->Name());

  // 遍历所有level,根据compact_pointer_[level],保存compaction点
  for (int level = 0; level < config::kNumLevels; level++) {
    if (!compact_pointer_[level].empty()) {
      InternalKey key;
      key.DecodeFrom(compact_pointer_[level]);
      edit.SetCompactPointer(level, key);
    }
  }

  // 遍历所有level,根据current_->files_,保存当前版本sstable文件集合
  for (int level = 0; level < config::kNumLevels; level++) {
    const std::vector<FileMetaData*>& files = current_->files_[level];
    for (size_t i = 0; i < files.size(); i++) {
      const FileMetaData* f = files[i];
      edit.AddFile(level, f->number, f->file_size, f->smallest, f->largest);
    }
  }
//序列化并append到log(MANIFEST文件)中
  std::string record;
  edit.EncodeTo(&record);
  return log->AddRecord(record);
}

其实WriteSnapshot就是把目前数据库的元数据信息,比如当前版本所有sst文件的元数据信息,用log的方式append到MANIFEST文件中,*注意这的versionedit和compact产生的versionedit的不同,这的versionedit记录的是当前版本的全量元数据信息,而compact产生的versionedit记录的仅仅是增量
元数据信息。*

uint64_tApproximateOffsetOf(Version* v, const InternalKey& ikey)

在指定的version中查找指定key的大概位置。
假设version中有n个sstable文件,并且落在了地i个sstable的key空间内,那么返回的位置= sstable1文件大小+sstable2文件大小 + … + sstable (i-1)文件大小
+ key在sstable i中的大概偏移。
可分为两段逻辑。
1 首先直接和sstable的max key作比较,如果key > max key,直接跳过该文件,还记得sstable文件是有序排列的。
对于level >0的文件集合而言,如果如果key < sstable文件的min key,则直接跳出循环,因为后续的sstable的min key肯定大于key。
2 key在sstable i中的大概偏移使用的是Table:: ApproximateOffsetOf(target)接口,前面分析过,它返回的是Table中>= target的key的位置。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值