您的位置:首页 > 理论基础 > 数据结构算法

leveldb之Version相关数据结构

2016-10-09 11:29 363 查看


leveldb之Version相关数据结构

2015-05-15 10:09 229人阅读 评论(0) 收藏 举报


 分类:

leveldb(13) 


版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[+]

在leveldb中,Version就代表了一个版本,它包括当前磁盘及内存中的所有文件信息。当执行一次compaction后,Leveldb将在当前版本基础上创建一个新版本,在所有的version中,只有一个是CURRENT。
VersionSet是所有Version的集合,用于管理所有的version。
VersionEdit记录了Version之间的变化,其中记录了基于上一Version增加了多少文件,删除了文件。也就是说:Version0 + VersionEdit --> Version1。
每次文件有变动时,leveldb就把变动记录到一个VersionEdit变量中,然后通过VersionEdit把变动应用到current version上,并把current version的快照,也就是db元信息保存到MANIFEST文件中。
另外,MANIFEST文件组织是以VersionEdit的形式写入的,它本身是一个log文件格式,采用log::Writer/Reader的方式读写,一个VersionEdit就是一条log record。
由之前 对leveldb文件命名的分析 可知,通过文件编号和文件类型就可以知道leveldb中文件的完整名称,并获得相应文件了,因此在下面可以看到很多数据结构中都保存的文件编号这一变量。

1、FileMetaData

leveldb中有很多SSTable,其中保存着Key值有序的数据,不同的SSTable文件之间的Key值区间没有重叠(level 0除外)。FileMetaData用于描述每一个.sst文件的信息,它记录了一个SSTable中的最小和最大的Key值,即Key值的变化区间,极大的提高了查找操作的效率。其数据结构如下:

[cpp] view
plain copy

 





struct FileMetaData {  

  int refs;//引用次数  

  int allowed_seeks;  //允许的最大查找次数  

  uint64_t number;    //.sst文件编号  

  uint64_t file_size;         // File size in bytes  

  InternalKey smallest;       // Smallest internal key served by table  

  InternalKey largest;        // Largest internal key served by table  

};  

allowed_seeks的值初始化一般如下:(文件大小除以16K,小于100时为100)

[cpp] view
plain copy

 





f->allowed_seeks = (f->file_size / 16384);  

if (f->allowed_seeks < 100) f->allowed_seeks = 100;  

代码中的解释为:

[cpp] view
plain copy

 





// We arrange to automatically compact this file aftera certain number of seeks.  Let's assume:  

      //   (1) One seek costs 10ms  

      //   (2) Writing or reading 1MB costs 10ms (100MB/s)  

      //   (3) A compaction of 1MB does 25MB of IO:  

      //         1MB read from this level  

      //         10-12MB read from next level (boundaries may be misaligned)  

      //         10-12MB written to next level  

      // This implies that 25 seeks cost the same as the compaction of 1MB of data.  I.e., one seek costs approximately the same as the compaction of 40KB of data.  We are a little conservative and allow approximately one seek for every 16KB of data before triggering a compaction.  

大概意思是,当对1M的数据执行Compaction(level + (level+1)  --->>>  new level+1)操作时,相当于要执行25M数据的IO操作。具体包括,从当前level中读取1M数据,读取level+1的10~12M数据,往level+1中写入10~12M数据(不是很懂)。而执行一次seek操作需要花费10ms,读写1M数据也需要10ms,因此执行25次seeks操作所花费的时间与compaction 1M(读写25M)数据操作的时间相当,这样每执行一次seek操作所用的时间与对1M/25
= 40K的数据执行compaction操作相当。保守考虑将其设为16K。

2、Version

leveldb通过Version来记录一个版本,Version其实是一系列SSTable的集合,SSTable文件是按照不同的level来存储的,不同的level可能有多个SSTable文件。Version中保存了所有level的所有FileMetaData文件,并通过next和prev指针形成双向循环链表,链表尾部元素即为最新的Version,一般也就是所谓的Current版本。Version的数据结构为:

[cpp] view
plain copy

 





class Version {  

 private:  

  VersionSet* vset_; // VersionSet to which this Version belongs  

  Version* next_;    // next_、prev形成双向链表  

  Version* prev_;                 

  int refs_;                      

  

  std::vector<FileMetaData*> files_[config::kNumLevels];//二维数组,记录所有level的全部FileMetaData文件  

  

  // 基于Seek的结果stats得到的下一个等待Compact的文件(当FileMetaData文件查找到一定次数时,就需要执行合并操作)  

  FileMetaData* file_to_compact_;  

  int file_to_compact_level_;//需要进行合并的文件所属的level  

  

  double compaction_score_;//当score>=1时,也需要进行合并  

  int compaction_level_;//需要进行合并的level  

};  

由上面的数据结构可以简单的画出Version的数据结构如下:



vset_指向Version所属的VersionSet,next_和prev_形成一个双向循环链表,file_to_compact_指向查找次数已经到达上限的等待执行合并操作的文件,files_是一个二维数组,files_[level][i]表示第level层的第i个SSTable文件。
类Version的主要成员函数为:

[cpp] view
plain copy

 





class Version {  

 public:  

  void AddIterators(const ReadOptions&, std::vector<Iterator*>* iters);  

  struct GetStats {  //查找相关的统计信息  

    FileMetaData* seek_file;  

    int seek_file_level;  

  };  

  Status Get(const ReadOptions&, const LookupKey& key, std::string* val,GetStats* stats);查找key值对应的val  

  bool UpdateStats(const GetStats& stats);//基于上面查找的统计信息来判断是否需要进行合并(文件查找次数到达一定值时需要进行合并)  

  

  void GetOverlappingInputs(int level,const InternalKey* begin,  

      const InternalKey* end,std::vector<FileMetaData*>* inputs);  

  bool OverlapInLevel(int level,const Slice* smallest_user_key,const Slice* largest_user_key);  

  

  //为Memtable合并的输出文件选择合适的level  

  int PickLevelForMemTableOutput(const Slice& smallest_user_key,const Slice& largest_user_key);  

 private:  

  void ForEachOverlapping(Slice user_key, Slice internal_key,void* arg,  

                          bool (*func)(void*, int, FileMetaData*));  

};  

由上可知,Version的成员函数主要是进行查找、判断当前Version是否需要进行合并以及为Memtable的合并选择合适的level,还有其他一些函数用于获取相关信息。

因此,一个Version中保存的是当前版本中各个level中的SSTable文件,以及等待合并的文件和level,并提供函数判断是否有文件需要进行合并,以及相关的level信息。

3、VersionEdit

VersionEdit用于表示Version的变化,通过原始Version加上一系列的VersionEdit可以得到最新的Version。

[cpp] view
plain copy

 





class VersionEdit {  

 public:  

  void AddFile(int level, uint64_t file,uint64_t file_size,const InternalKey& smallestconst InternalKey& largest);  

  void DeleteFile(int level, uint64_t file) ;  

 private:  

  friend class VersionSet;  

  //通过set来记录要删除的文件,利用set可以保证变量不重复,即不会导致重复删除某个文件的错误  

  typedef std::set< std::pair<int, uint64_t> > DeletedFileSet;  

  

  std::string comparator_;  

  uint64_t log_number_;  

  uint64_t prev_log_number_;  

  uint64_t next_file_number_;  

  SequenceNumber last_sequence_;  

  bool has_comparator_;  

  bool has_log_number_;  

  bool has_prev_log_number_;  

  bool has_next_file_number_;  

  bool has_last_sequence_;  

  

  std::vector< std::pair<int, InternalKey> > compact_pointers_;  

  DeletedFileSet deleted_files_;  

  std::vector< std::pair<int, FileMetaData> > new_files_;  

};  

由上可知,VersionEdit通过两个数组new_files_和deleted_files_来保存针对当前Version要增加和删除的文件,通过AddFile()和DeleteFile()两个操作来实现。

然后通过VersionSet::LogAndApply(VersionEdit* edit)来实现将VersionEdit应用到某个Version,来生成一个新的Version。

对于Memtable的Compaction来说,就是将Memtable转化为level 0的.sst文件,即创建.sst文件,并将Memtable中的数据写入.sst文件。然后将生成的.sst文件加入到VersionEdit的new_files_中即可。这样在后面调用LogAndApply()时,只需要将VersionEdit中新增加的.sst文件加入到新的Version的level 0中,就可以和原始Version一起生成新的Version了。

对于更高level的合并,是将level 和level +1 合并在一起,生成新的level +1,这样就需要创建新的.sst文件来存储level和level +1合并后新生成的.sst文件,而原来的level和level+1中的文件就可以直接删除了。因此将新生成的.sst文件加入到VersionEdit的new_files_中,将合并的输入文件调用Compaction::AddInputDeletions(edit)将其加入到VersionEdit的deleted_files_。然后在后面调用LogAndApply()时就可以在原来版本的基础上,删除输入的level和level+1中的文件,再向level+1增加合并生成的.sst文件就可以生成新的Version了。

4、VersionSet

VersionSet是一系列的Version的集合,用于管理所有版本。

[cpp] view
plain copy

 





class VersionSet {  

 public:  

  Status LogAndApply(VersionEdit* edit, port::Mutex* mu)//应用VersionEdit中保存的信息,来形成新的Version  

      EXCLUSIVE_LOCKS_REQUIRED(mu);  

  Compaction* PickCompaction();//选择需要进行合并的level和level、level+1的文件,为空时表示不需要进行合并  

  

  bool NeedsCompaction() const {  

    Version* v = current_;  

    return (v->compaction_score_ >= 1) || (v->file_to_compact_ != NULL);//当前版本有一个条件满足即需要执行合并  

  }  

 private:  

  class Builder;  

  

  void SetupOtherInputs(Compaction* c);  

  Status WriteSnapshot(log::Writer* log);  

  void AppendVersion(Version* v);  

  

  Env* const env_;  

  const std::string dbname_;  

  const Options* const options_;  

  TableCache* const table_cache_;//缓存部分SSTable  

  const InternalKeyComparator icmp_;  

  uint64_t next_file_number_;   //下一个要生成的文件的编号  

  uint64_t manifest_file_number_;  

  uint64_t last_sequence_;  

  uint64_t log_number_;  

  uint64_t prev_log_number_;  // 0 or backing store for memtable being compacted  

  

  // Opened lazily  

  WritableFile* descriptor_file_;//数据库的Manifest清单文件  

  log::Writer* descriptor_log_;//用于写Manifest文件  

  Version dummy_versions_;  // 所有Version形成的双向链表的头部  

  Version* current_;        // == dummy_versions_.prev_,双向链表的尾部,即最新的Version  

  

  std::string compact_pointer_[config::kNumLevels];//每一level下次要执行合并操作的起始Key值  

};  

由上面的数据结构可以简单的画出其结构如下:



由上可知,VersionSet通过变量dummy_versions_来保存所有Version形成的双向链表的头部,用来获取所有的版本。current_指向链表的尾部元素,即为最新的Version。

一个VersionSet是用于管理所有Version的,其中保存的主要成员变量包括table_cache、Manifest文件、各个版本形成的双向链表和一些编号,主要提供的操作是应用VersionEdit形成新的Version、对各个编号的管理以及合并相关的操作。

5、Compaction

Compaction用于保存等待执行合并操作的输入数据及相关信息,每一次合并都是将level与level+1中的文件进行合并,来生成新的level+1层文件

[cpp] view
plain copy

 





// A Compaction encapsulates information about a compaction.  

class Compaction {  

 public:  

  void AddInputDeletions(VersionEdit* edit);//此次合并操作的输入文件都作为要删除的文件加入到edit中,即删除原来的level和level+1层的文件,只保留新生成的level+1层文件即可  

  bool ShouldStopBefore(const Slice& internal_key);//判断在处理internal_key之前,释放应该停止此次合并操作  

  void ReleaseInputs();//当成功完成合并操作后,释放输入数据  

 private:  

  int level_;  

  uint64_t max_output_file_size_;  

  Version* input_version_;//等待合并的版本,一般为current  

  VersionEdit edit_;//针对input_version_所做的更改  

  

  std::vector<FileMetaData*> inputs_[2];      // 等待合并的数据,即level和level+1层的FileMetaData文件  

  

  std::vector<FileMetaData*> grandparents_;//level+2层的FileMetaData文件  

  size_t grandparent_index_;  // Index in grandparent_starts_  

  bool seen_key_;             // Some output key has been seen  

  int64_t overlapped_bytes_;  // 当前合并的输出文件与level+2层中的文件的Key值重合的文件大小  

  

  size_t level_ptrs_[config::kNumLevels];//保存了此次合并操作涉及的从level+2开始的每一层的文件起始位置  

};  

一个Compaction中保存的主要是等待合并的版本、基于该版本的变化、以及等待合并的文件和level。提供的主要操作是对此次Compaction的一些判断,包括是否应该停止合并、此次合并是否Trivial。

8、config

config中配置了一些默认的参数,如下所示:

[cpp] view
plain copy

 





namespace config {  

static const int kNumLevels = 7;//leveldb中SSTable文件的最大层数  

  

static const int kL0_CompactionTrigger = 4;//当level 0有这么多文件时,就会触发level 0的compaction  

  

static const int kL0_SlowdownWritesTrigger = 8;//level 0中的文件个数的软限制,当被触发时会减慢写入的速度  

  

static const int kL0_StopWritesTrigger = 12;//level 0最大文件个数,当被触发时,会停止写入  

  

static const int kMaxMemCompactLevel = 2;//对Memtable执行合并操作时,允许的最大合并层数,即最多只能合并到level 2  

  

static const int kReadBytesPeriod = 1048576;// Approximate gap in bytes between samples of data read during iteration.  

}  // namespace config  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: