博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
leveldb源码剖析---版本管理
阅读量:4165 次
发布时间:2019-05-26

本文共 5157 字,大约阅读时间需要 17 分钟。

所谓的版本,简单地说,指的是leveldb中各个level层的文件信息。显然,随着compaction的进行和新的memtable写入生成新的sstable,版本会不断变化。版本除了记录各层的文件信息外,还记录各层关于compaction的的信息,比如在对于当前版本,最适合进行compaction的level是哪层,以及这层中最适合compaction的是哪个文件等信息。

和版本相关的类主要是以下三个:

  1. class Version
  2. class VersionSet
  3. class VersionEdit

其中version是单独一个版本的信息,VersionSet是版本信息的集合,里面包含多个通过链表组织的版本,VersionEdit主要是记录版本的变化信息。下面我们将逐一介绍这三个类,以及通过观察版本的变化来了解它们的作用以及如何工作。


先来看一下版本(Version)的数据成员

这里写图片描述

vset_指针是指向包含该版本的版本集合,一般每个leveldb实例中都有一个版本集合,用于管理从启动开始产生的所有版本信息。因为版本集合是通过链表的信息对版本进行管理,因此下面两个数据成员就是链表指针。refs_是一个计数器,用于生命周期管理。再继续就是对应该版本的各个层的文件信息 了,这是一个二维数组,数组中的每个子数组代表一个层,子数组中的每个元素是对应该层的一个文件的元信息。最后面三个成员主要是用于统计当前版本的compaction信息,通过这些信息可以快速判断应该对哪个level进行compaction。


VersionEdit**用于记录对版本的修改**,其中的关键数据成员是deleted_files_和new_files_。deleted_files是一个set,包含在当前版本在此次修改中被删除的文件,new_files_是一个数组,记录此次修改中新增加的文件。

因此通过从当前版本中的删去包含在deleted_files_中的文件,以及添加包含在new_files_中的文件,就可以得到一个新的版本。


leveldb通过VersionSet管理自数据库启动开始的所有版本。通过一个链表将所有版本连接起来,最新的版本位于链表尾部。这个链表是一个双向链表,它的头部用dummy_versions_数据成员标记。VersionSet_中的current_数据成员指向当前数据库的版本信息。VersionSet_中的数据成员compact_pointer_数组主要是标记每个level下次进行compaction时应该选择哪个文件。compact_pointer_里面是一个key数组,比如如果下次进行compaction的是level i,则应该选择level i中第一个key大于compact_pointer_[i]的文件,同时会更新compact_pointer_[i],保证下次compaction会从一个新的文件进行。

VersionSet除了管理版本的文件信息之外,还会管理数据库的缓存,日志信息等等内容。

下面我们通过一个实际的版本变化过程看一下版本管理的工作流程。


触发版本变化的主要有两个地方

  1. 将memtable写入磁盘,生成新的sstable
  2. compaction操作

下面我们分别看一下在这两个操作下,版本信息是怎么进行管理的。


memtable写盘

这个过程主要是在DBImpl::CompactMemTable函数中完成

以下我们摘取这个函数的部分代码分析一下

VersionEdit edit;  Version* base = versions_->current();  base->Ref();  Status s = WriteLevel0Table(imm_, &edit, base);  base->Unref();

这里主要是申请了一个VersionEdit变量,并把他传入到WriteLevel0Table函数中,用于记录新生成的sstable文件信息。我们追踪到WriteLevel0Table函数里面就可以看到下面的代码:

edit->AddFile(level, meta.number, meta.file_size,                  meta.smallest, meta.largest);

这个函数将新生成的sstable文件的元信息加入到VersionEdit的new_files_中。

再回到CompactMemTable函数:

if (s.ok()) {    edit.SetPrevLogNumber(0);    edit.SetLogNumber(logfile_number_);  // Earlier logs no longer needed    s = versions_->LogAndApply(&edit, &mutex_);   }

这个就是版本管理的关键了。其中LogAndApply函数将根据前面记录的版本修改信息更新当前版本,得到一个新的版本信息。同时把这个新的版本信息设置成当前VersionSet的current_,并连入版本集合的链表中。

这个函数我们后面再介绍,这里我们只需要知道根据修改信息生成新的版本就可以了。

可以看到,对于CompactMemTable,版本的修改信息很简单,就是在new_files_里面添加一个新的文件元信息。


compaction操作

这个工作主要是在函数DBImpl::BackgroundCompaction中完成。前面我们分析的时候知道,对level和level+1中的文件进行compaction时会有两中情况

  1. 当level+1中没有文件和level中的那个待compaction的文件的key范围重合时,此时直接将level中的那个文件移动到level+1中的那个文件即可。
  2. level+1中有文件和level中的那个文件key范围重合。则进行常规的compaction。

下面我们分别从这两种情况看一下版本管理流程。

else if (!is_manual && c->IsTrivialMove()) { //当input[0](level)中只有一个需要compaction的文件,input[1](level+1)中没有需要compaction的文件    // Move file to next level    assert(c->num_input_files(0) == 1);    FileMetaData* f = c->input(0, 0); // 将 f 从 c->level移动到 c->level + 1中即可    c->edit()->DeleteFile(c->level(), f->number);    c->edit()->AddFile(c->level() + 1, f->number, f->file_size,                       f->smallest, f->largest);    status = versions_->LogAndApply(c->edit(), &mutex_); //只需要在edit中记录就可以了

对于第一种情况,VersionEdit将level中的那个文件的元信息删除。同时把这个元信息移动到level+1中,最后调用LogAndApply更新当前版本信息。

对于第二种情况,主要是由DoCompactionWork函数中InstallCompactionResults函数的完成。

BackgroundCompaction -> DoCompactionWork -> InstallCompactionResults

下面分析一个InstallCompactionResults函数:

compact->compaction->AddInputDeletions(compact->compaction->edit());  const int level = compact->compaction->level();

前面我们分析过compaction->input数组中包含了本次compaction操作所需要的所有sstable文件信息,同时compaction->output中包含了所有新生成的文件信息,因此我们很自然的想法是将inputs_中的文件信息加入到VersionEdit的deleted_files中,而将outputs中的文件信息加入到它的new_files_中,InstallCompactionResults正是这么做的。上面的AddInputDeletions函数就是将input_的文件信息加入到edit的deleted_files中,而下面的for循环

for (size_t i = 0; i < compact->outputs.size(); i++) {
const CompactionState::Output& out = compact->outputs[i]; compact->compaction->edit()->AddFile( level + 1, out.number, out.file_size, out.smallest, out.largest); }

则将output_中的文件信息加入到new_files数组中。

versions_->LogAndApply(compact->compaction->edit(), &mutex_);

最后一如既往调用LogAndApply函数将修改信息更新到当前版本,生成一个新的版本信息。

下面我们分析一下LogAndApply函数


LogAndApply函数的实现

用一个式子可以完全概括LogAndApply函数的作用:

v = current_ + edit;

其中current是当前版本的信息,edit是版本的修改信息,v是在当前版本上修改得到的新的版本信息。

我们从下面的代码开始看起:

Version* v = new Version(this);  {    Builder builder(this, current_);     builder.Apply(edit);    builder.SaveTo(v);  }

这里先new一个空的版本信息,然后将current_经过edit更新后得到的新版本保存到v中。其实说白了,就是将edit的new_files_中的文件信息添加到current的文件信息中,将edit的deleted_files中的文件信息从current中删除掉,这样就得到了一个新的版本,将这个版本赋值给v。

Finalize(v);// Precomputed best level for next compaction

正如注释所说,这里是计算对于这个新版本的compaction操作信息,比如算出哪个level最适合进行compaction,以备下次compaction的时候使用。所以主要工作就是计算version->compaction_level_和version->compaction_score_。然后会将这个edit写入到manifest文件中。这涉及到日志系统,我们后面再介绍。最后的AppendVersion(v)会将这个新的version设置到当前数据库的versionset中,以及将数据库的当前版本current_也设置为v。

到此,我们基本就把leveldb的版本管理介绍完毕了。


总结

一个健壮的数据库系统,版本信息是必不可少的。leveldb的版本信息主要是记录数据库的文件信息。随着compaction操作和memtable的写盘,数据库中的文件会不断发生变化,因此对于每次可能生成新的sstable文件或者可能删除已有的sstable文件的操作,都必须进行版本更新。这里我们从以上两个操作来跟踪了解了leveldb的版本更新工作流程。leveldb通过versionedit记录版本的修改信息,然后通过LogAndApply函数将这个修改信息更新到当前的版本(current_),生成新的版本信息。leveldb还通过versionSet将从系统启动开始经历的所有版本信息放在一个集合中进行管理,这个集合是用链表支持的,最新的版本总是Append到链表的尾部。

你可能感兴趣的文章
SQL语句集锦【强力推荐!】
查看>>
两阶段提交协议
查看>>
数据库事务和锁
查看>>
编辑本段数据库结构与数据库种类
查看>>
strcat出现的问题
查看>>
php-APC介绍
查看>>
使用CSS处理长字符串
查看>>
B树、B-树、B+树、B*树
查看>>
模板引擎SMARTY
查看>>
对HTTP协议的头信息详解
查看>>
HTTP协议基础
查看>>
cookie和session机制之间的区别与联系
查看>>
动态联编(晚捆绑)
查看>>
什么是网络编程
查看>>
多线程编程
查看>>
hash_map和map的区别
查看>>
strcpy & strncpy
查看>>
itoa函数及atoi函数
查看>>
ActionScript
查看>>
设计模式之Singleton(单件)
查看>>