leveldb:version分析

class Version {
 public:
  //生成iterator用于遍历
  void AddIterators(const ReadOptions&, std::vector<Iterator*>* iters);

  //根据key来查询,GetStats记录了第一个读取的文件
  struct GetStats {
    FileMetaData* seek_file;
    int seek_file_level;
  };
  Status Get(const ReadOptions&, const LookupKey& key, std::string* val,
             GetStats* stats);

  //更新GetStats中文件的allowseek,决定是否需要进行compaction
  bool UpdateStats(const GetStats& stats);

  //引用计算,避免在被引用时候删除
  void Ref();
  void Unref();

  //查询和key range有关的files
  void GetOverlappingInputs(
      int level,
      const InternalKey* begin,         // NULL means before all keys
      const InternalKey* end,           // NULL means after all keys
      std::vector<FileMetaData*>* inputs);

  // 如果指定level中的某些文件和[*smallest_user_key,*largest_user_key]有重合就返回true。
  bool OverlapInLevel(int level,
                      const Slice* smallest_user_key,
                      const Slice* largest_user_key);

  //返回我们应该在哪个level上放置新的memtable compaction
  int PickLevelForMemTableOutput(const Slice& smallest_user_key,
                                 const Slice& largest_user_key);

  //某个level的文件个数
   int NumFiles(int level) const { return files_[level].size(); }

  // Return a human readable string that describes this version's contents.
  std::string DebugString() const;

 private:
  friend class Compaction;
  friend class VersionSet;

  class LevelFileNumIterator;
  Iterator* NewConcatenatingIterator(const ReadOptions&, int level) const;

  VersionSet* vset_;            // VersionSet to which this Version belongs
  Version* next_;               // Next version in linked list
  Version* prev_;               // Previous version in linked list
  int refs_;                    // Number of live refs to this version

  //sst files 
  std::vector<FileMetaData*> files_[config::kNumLevels];

  //下一个要被compaction的文件
  FileMetaData* file_to_compact_;
  int file_to_compact_level_;

  //compaction score:>1表示要compaction
  double compaction_score_;
  int compaction_level_;

  explicit Version(VersionSet* vset)
      : vset_(vset), next_(this), prev_(this), refs_(0),
        file_to_compact_(NULL),
        file_to_compact_level_(-1),
        compaction_score_(-1),
        compaction_level_(-1) {
  }

  ~Version();

  // No copying allowed
  Version(const Version&);
  void operator=(const Version&);
};
Version::AddIterators()

该函数最终在DB::NewIterators()接口中被调用,调用层次为:
DBImpl::NewIterator()->DBImpl::NewInternalIterator()->Version::AddIterators()。
函数功能是为该Version中的所有sstable都创建一个Two Level Iterator,以遍历sstable的内容。

void Version::AddIterators(const ReadOptions& options,
                           std::vector<Iterator*>* iters) {
 /* 对于level=0级别的sstable文件,直接通过TableCache::NewIterator()接口创建,这会直
 接载入sstable文件到内存cache中,这可能是因为第0层sst文件被读的概率更高,可能因为第0层无
 序,所以一个文件对应一个Iterator。*/
  for (size_t i = 0; i < files_[0].size(); i++) {
    iters->push_back(
        vset_->table_cache_->NewIterator(
            options, files_[0][i]->number, files_[0][i]->file_size));
  }

/*对于level>0级别的sstable文件,通过函数NewTwoLevelIterator()创建一个TwoLevelIterator,
这就使用了lazy open的机制,其中每层对应一个Iterator,因为>0层的sst都是有序无重叠的。*/
  for (int level = 1; level < config::kNumLevels; level++) {
    if (!files_[level].empty()) {
      iters->push_back(NewConcatenatingIterator(options, level));
    }
  }
}
Iterator* Version::NewConcatenatingIterator(const ReadOptions& options,
                                            int level) const {
  return NewTwoLevelIterator(
      new LevelFileNumIterator(vset_->icmp_, &files_[level]),
      &GetFileIterator, vset_->table_cache_, options);
}

函数NewConcatenatingIterator()直接返回一个TwoLevelIterator对象:
其第一级iterator是一个LevelFileNumIterator,第二级的迭代函数是GetFileIterator,下面就来分别分析之。
GetFileIterator是一个静态函数,很简单,直接返回TableCache::NewIterator()。

/*这里的file_value是取自于LevelFileNumIterator的value,它的value()函数
把file number和size以Fixed 8byte的方式压缩成一个Slice对象并返回。*/
static Iterator* GetFileIterator(void* arg,
                                 const ReadOptions& options,
                                 const Slice& file_value) {
  TableCache* cache = reinterpret_cast<TableCache*>(arg);
  if (file_value.size() != 16) {
    return NewErrorIterator(
        Status::Corruption("FileReader invoked with unexpected value"));
  } else {
    return cache->NewIterator(options,
                              DecodeFixed64(file_value.data()),
                              DecodeFixed64(file_value.data() + 8));
  }
Version::LevelFileNumIterator类

这也是一个继承者Iterator的子类,一个内部Iterator。给定一个version/level对,生成该level内的文件信息。对于给定的entry,key()返回的是文件中所包含的最大的key,value()返回的是|file number(8 bytes)|file size(8 bytes)|串。
它的构造函数接受两个参数:InternalKeyComparator&,用于key的比较;vector

class Version::LevelFileNumIterator : public Iterator {
 public:
  LevelFileNumIterator(const InternalKeyComparator& icmp,
                       const std::vector<FileMetaData*>* flist)
      : icmp_(icmp),
        flist_(flist),
        index_(flist->size()) {        // Marks as invalid
  }
  virtual bool Valid() const {
    return index_ < flist_->size();
  }
  virtual void Seek(const Slice& target) {
    index_ = FindFile(icmp_, *flist_, target);
  }
  virtual void SeekToFirst() { index_ = 0; }
  virtual void SeekToLast() {
    index_ = flist_->empty() ? 0 : flist_->size() - 1;
  }
  virtual void Next() {
    assert(Valid());
    index_++;
  }
  virtual void Prev() {
    assert(Valid());
    if (index_ == 0) {
      index_ = flist_->size();  // Marks as invalid
    } else {
      index_--;
    }
  }
  //返回的是当前所在文件中所包含的最大的key
  Slice key() const {
    assert(Valid());
    return (*flist_)[index_]->largest.Encode();
  }
  //返回的是|file number(8 bytes)|file size(8 bytes)|串。
  Slice value() const {
    assert(Valid());
    EncodeFixed64(value_buf_, (*flist_)[index_]->number);
    EncodeFixed64(value_buf_+8, (*flist_)[index_]->file_size);
    return Slice(value_buf_, sizeof(value_buf_));
  }
  virtual Status status() const { return Status::OK(); }
 private:
  const InternalKeyComparator icmp_;
  const std::vector<FileMetaData*>* const flist_;
  uint32_t index_;

  // Backing store for value().  Holds the file number and size.
  mutable char value_buf_[16];
};

来看Seek中用到的FindFile,这其实是一个二分查找函数,因为传入的sstable文件列表是有序的,因此可以使用二分查找算法。
找到一个key范围包含指定key的sst文件,返回该sst文件在files中的下标。

int FindFile(const InternalKeyComparator& icmp,
             const std::vector<FileMetaData*>& files,
             const Slice& key) {
  uint32_t left = 0;
  uint32_t right = files.size();
  while (left < right) {
    uint32_t mid = (left + right) / 2;
    const FileMetaData* f = files[mid];
    if (icmp.InternalKeyComparator::Compare(f->largest.Encode(), key) < 0) {
      left = mid + 1;
    } else {
      right = mid;
    }
  }
  return right;
}

Version::Get()

查找函数,直接在DBImpl::Get()中被调用,如果本次Get不止seek了一个文件(仅会发生在level 0的情况),就将搜索的第一个文件保存在stats中。如果stat有数据返回,表明本次读取在搜索到包含key的sstable文件之前,还做了其它无谓的搜索。这个结果将用在UpdateStats()中,可能会引发合并。

Status Version::Get(const ReadOptions& options,
                    const LookupKey& k,
                    std::string* value,
                    GetStats* stats) {
  Slice ikey = k.internal_key();
  Slice user_key = k.user_key();
  const Comparator* ucmp = vset_->icmp_.user_comparator();
  Status s;

  stats->seek_file = NULL;
  stats->seek_file_level = -1;
  FileMetaData* last_file_read = NULL;//记录上一次读到的文件
  int last_file_read_level = -1;//记录上一次读到的文件所在的层次
//用于0层key范围包含k的文件,因为0层文件key范围可以重叠,所以可能有多个
  std::vector<FileMetaData*> tmp;
 //大于0层的每层只可能有一个key范围包含k的文件
  FileMetaData* tmp2;
  for (int level = 0; level < config::kNumLevels; level++) {
    size_t num_files = files_[level].size();
    if (num_files == 0) continue;//本层没有文件,则直接跳过

    // 取得该level下的所有sstable文件列表,搜索本层
    FileMetaData* const* files = &files_[level][0];
    if (level == 0) {
    //第0层比较特殊,因为0层文件可能相互重叠,最多整个0层文件都包含该k,为tmp预设最大空间
      tmp.reserve(num_files);
      for (uint32_t i = 0; i < num_files; i++) {
       //遍历0层的每一个文件,若该文件key范围包含k,则添加进tmp
        FileMetaData* f = files[i];
        if (ucmp->Compare(user_key, f->smallest.user_key()) >= 0 &&
            ucmp->Compare(user_key, f->largest.user_key()) <= 0) {
          tmp.push_back(f);
        }
      }
      if (tmp.empty()) continue;
      //对符合条件的文件按由新到旧排序
      std::sort(tmp.begin(), tmp.end(), NewestFirst);
      files = &tmp[0];
      num_files = tmp.size();
    } else {
     //对于level>0,leveldb保证sstable文件之间不会有重叠,所以处理逻辑有别于
     //level 0,直接根据ikey定位到sstable文件即可。
     //二分查找,找到第一个largest key >=ikey的file index
      uint32_t index = FindFile(vset_->icmp_, files_[level], ikey);
      if (index >= num_files) {
        files = NULL;
        num_files = 0;
      } else {
        tmp2 = files[index];
        if (ucmp->Compare(user_key, tmp2->smallest.user_key()) < 0) {
           // 找到的文件其所有key都大于user_key,等于文件不存在
          files = NULL;
          num_files = 0;
        } else {
          files = &tmp2;
          num_files = 1;
        }
      }
    }

    //遍历找到的文件,找到的文件存在files中,其个数为num_files。
    for (uint32_t i = 0; i < num_files; ++i) {
      if (last_file_read != NULL && stats->seek_file == NULL) {
        // 如果寻找k所读取的sst文件不止一个文件,记录第一个读取的sst文件以及所在层
        stats->seek_file = last_file_read;
        stats->seek_file_level = last_file_read_level;
      }

      FileMetaData* f = files[i];
      last_file_read = f;// 记录本次读取的level和file
      last_file_read_level = level;

      Saver saver;
      saver.state = kNotFound;
      saver.ucmp = ucmp;
      saver.user_key = user_key;
      saver.value = value;
     // 调用TableCache::Get()尝试获取{ikey, value},如果返回失败则直接返回
      s = vset_->table_cache_->Get(options, f->number, f->file_size,
                                   ikey, &saver, SaveValue);
      if (!s.ok()) {
        return s;
      }
      //根据saver的状态判断,如果是Not Found则向下搜索下一个更早的sst文件,其它值则返回。
      switch (saver.state) {
        case kNotFound:
          break;      // 继续搜索下一个更早的sstable文件
        case kFound:
          return s;// 找到  
        case kDeleted:
          s = Status::NotFound(Slice());  // 已删除
          return s;
        case kCorrupt:
          s = Status::Corruption("corrupted key for ", user_key);
          return s;// 数据损坏 
      }
    }
  }

  return Status::NotFound(Slice());  // Use an empty error message for speed
}

以上就是Version::Get()的代码逻辑,如果level 0的sstable文件太多的话,会影响读取速度,这也是为什么进行compaction的原因

Version::UpdateStats()

当Get操作直接搜寻memtable没有命中时,就需要调用Version::Get()函数从磁盘load数据文件并查找。如果此次Get不止seek了一个文件,就记录第一个文件到stat并返回。其后leveldb就会调用UpdateStats(stat)。
Stat表明在指定key range查找key时,都要先seek此文件,才能在后续的sstable文件中找到key。
该函数是将stat记录的sstable文件的allowed_seeks减1,减到0就执行compaction。也就是说如果文件被seek的次数超过了限制,表明读取效率已经很低,需要执行compaction了。所以说allowed_seeks是对compaction流程的有一个优化。
变量allowed_seeks的值在sstable文件加入到version时确定,也就是后面将遇到的VersionSet::Builder::Apply()函数。

Version::GetOverlappingInputs()

它在指定level中找出和[begin, end]有重合的sstable文件。
要注意的是,对于level0,由于文件可能有重合,其处理具有特殊性。当在level 0中找到有sstable文件和[begin, end]重合时,会相应的将begin/end扩展到文件的min key/max key,然后重新开始搜索。

Version::OverlapInLevel()

bool Version::OverlapInLevel(int level,
const Slice*smallest_user_key, const Slice* largest_user_key)
检查是否和指定level的文件有重合

Version::PickLevelForMemTableOutput()

函数返回我们应该在哪个level上放置新的memtable compaction,这个compaction覆盖了范围[smallest_user_key,largest_user_key]。
该函数的调用链为:
DBImpl::RecoverLogFile/DBImpl::CompactMemTable -> DBImpl:: WriteLevel0Table->Version::PickLevelForMemTableOutput

如果level 0没有找到重合就向下一层找,最大查找层次为kMaxMemCompactLevel = 2。如果在level 0or1找到了重合,就返回level 0。否则查找level 2,如果level 2有重合就返回level 1,否则返回level 2。
这个函数在整个compaction逻辑中的作用在分析DBImpl时再来结合整个流程分析,现在只需要了解它找到一个level存放新的compaction就行了。
如果返回level = 0,表明在level 0或者1和指定的range有重叠;如果返回1,表明在level2和指定的range有重叠;否则就返回2(kMaxMemCompactLevel)。
也就是说在compactmemtable的时候,写入的sstable文件不一定总是在level 0,如果比较顺利,没有重合的,它可能会写到level1或者level2中。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值