leveldb version机制

  该文章主要回答三个问题:

  •   leveldb 怎么管理因compact带来的文件变化?

  •   当db关闭后重新打开时,如何恢复状态?

  •   如何解决版本销毁文件变化和已经获取过的迭代器的冲突?

  每次leveldb后台进行compact时, 会造成sst文件的变化。levedb利用version来管理了这些变化。

compact前为Version1, compact后为Version2.
 VersionSet利用链表将前后一系列的version组织起来。核心代码在db/version_set.h
 db/version_set.cpp

  version间的变化通过VersionEdit来表示:  


1   std::vector< std::pair<int, InternalKey> > compact_pointers_; //表示level上下次可以开始compact的key值
2 DeletedFileSet deleted_files_;//表示这次删除的文件
3 std::vector< std::pair<int, FileMetaData> > new_files_;//表示这次删除的文件

  那么将如何从旧版本生成新版本了?看下下段VersionSet::LogAndApply的代码:


1   Version* v = new Version(this);
2 {
3 Builder builder(this, current_);
4 builder.Apply(edit); //将版本的变化即VersionEdit应用到VersionSet的compact_pointers, deleted_files及added_files。
5 builder.SaveTo(v);//根据VersionSet中的deleted_files以及added_files, 从base Version(即current_)生成新Version的数据内容
6 }
7 Finalize(v);

  详细看下SaveTo的过程


 1   void SaveTo(Version* v) {
2 BySmallestKey cmp;
3 cmp.internal_comparator = &vset_->icmp_;
4 for (int level = 0; level < config::kNumLevels; level++) {
5 // Merge the set of added files with the set of pre-existing files.
6 // Drop any deleted files. Store the result in *v.
7 const std::vector<FileMetaData*>& base_files = base_->files_[level];//一个根据根据file的最小key值排序的有序vector
8 std::vector<FileMetaData*>::const_iterator base_iter = base_files.begin();
9 std::vector<FileMetaData*>::const_iterator base_end = base_files.end();
10 const FileSet* added = levels_[level].added_files; //FileSet是一个根据file的最小key值排序的有序set
11 v->files_[level].reserve(base_files.size() + added->size());
      //如下过程类似于归并排序
      //base files:{2}, {5}, {7}
//add files:{1}, {6}
//过程就是{1}->files, {2}, {5}->files, {6}->files, {7}->files.
//当然,上步骤的过程中,还需要判断下该file是否能add到files中去,即MaybeAddFile(不在删除files里即可添加)

12 for (FileSet::const_iterator added_iter = added->begin();
13 added_iter != added->end();
14 ++added_iter) {
15 // Add all smaller files listed in base_
16 for (std::vector<FileMetaData*>::const_iterator bpos
17 = std::upper_bound(base_iter, base_end, *added_iter, cmp);
18 base_iter != bpos;
19 ++base_iter) {
20 MaybeAddFile(v, level, *base_iter);
21 }
22
23 MaybeAddFile(v, level, *added_iter);
24 }
25
26 // Add remaining base files
27 for (; base_iter != base_end; ++base_iter) {
28 MaybeAddFile(v, level, *base_iter);
29 }
30
31 }
32 }

  这段代码让我感兴趣的是很好的利用了std::upper_bound, 要是自己实现,估计就会自己去写归并的逻辑了。

  此外,当leveldb在运行时记录了每次的版本变化,并将其持久化于manifest文件中。当leveldb重新打开时,将从manifest文件中Recover出上一次levedb运行时的每次版本变化,并通过VersionSet::Builder
Apply每次版本变化到当前版本对象中(current_)。

  那么,难道manifest文件一直都保存这版本的历史变化么?答案当然是no,  在DB::open时就会调用LogAndApply.


 1   std::string new_manifest_file;
2 Status s;
3 if (descriptor_log_ == NULL) {
//DB::open时就会运行到这,因此会新建一个manifest_file, 此时的manifest_file_number就与当前的manifest文件不同。
//然后通过WriteSnapshot将当然的状态写到该manifest_file,因此manifest_file就只有一个版本变化了,这个版本变化融合了之前所有的历史变化。
//最后又进行了current文件指到该新manifest文件的操作,就完成了manifest_file替换。
 4     // No reason to unlock *mu here since we only hit this path in the
5 // first call to LogAndApply (when opening the database).
6 assert(descriptor_file_ == NULL);
7 new_manifest_file = DescriptorFileName(dbname_, manifest_file_number_);
8 edit->SetNextFile(next_file_number_);
9 s = env_->NewWritableFile(new_manifest_file, &descriptor_file_);
10 if (s.ok()) {
11 descriptor_log_ = new log::Writer(descriptor_file_);
12 s = WriteSnapshot(descriptor_log_);
13 }
14 }

  还有一个问题,在某个版本Version1上通过DBImpl::NewIterator获取了iterator后。如果进行compact,Version1对象以及Version1中的文件会不会销毁掉?答案肯定时no,
那么它时基于什么来实现的了?看下面一段代码


 1 Iterator* DBImpl::NewInternalIterator(const ReadOptions& options,
2 SequenceNumber* latest_snapshot) {
3 IterState* cleanup = new IterState;
4 mutex_.Lock();
5 *latest_snapshot = versions_->LastSequence();
6
7 // Collect together all needed child iterators
8 std::vector<Iterator*> list;
9 list.push_back(mem_->NewIterator());
10 mem_->Ref();//memtable引用计数+1
11 if (imm_ != NULL) {
12 list.push_back(imm_->NewIterator());
13 imm_->Ref();//immutable memtable引用计数+1
14 }
15 versions_->current()->AddIterators(options, &list);
16 Iterator* internal_iter =
17 NewMergingIterator(&internal_comparator_, &list[0], list.size());
18 versions_->current()->Ref();//当前version的引用计数+1
19
20 cleanup->mu = &mutex_;
21 cleanup->mem = mem_;
22 cleanup->imm = imm_;
23 cleanup->version = versions_->current();
//注册了CleanupIteratorState,这里就是当释放iterator时,会进行memtable, immutable memtable,  version的引用计数-1
24 internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, NULL);
25
26 mutex_.Unlock();
27 return internal_iter;
28 }

  从上面代码可以看出,通过引用计数避免了version的销毁。从而version中相对应的文件也不会销毁,可以从void
DBImpl::DeleteObsoleteFiles() 中看出,每次删除文件时,遍历所有的version,找出当前的live files,
即每个版本的file总集合。不在live files里才会被删除。

  

leveldb version机制,布布扣,bubuko.com

时间: 2024-12-12 16:48:10

leveldb version机制的相关文章

LevelDB Version

[LevelDB Version] Version 保存了当前磁盘以及内存中所有的文件信息,一般只有一个Version叫做"current" version(当前版本).Leveldb还保存了一系列的历史版本,这些历史版本有什么作用呢? 当一个Iterator创建后,Iterator就引用到了current version(当前版本),只要这个Iterator不被delete那么被Iterator引用的版本就会一直存活.这就意味着当你用完一个Iterator后,需要及时删除它. 当一次

[levelDB] Version Manager

一.作用 LevelDB如何能够知道每一层有哪些SST文件:如何快速的定位某条数据所在的SST文件:重启后又是如何恢复到之前的状态的,等等这些关键的问题都需要依赖元信息管理模块.对其维护的信息及所起的作用简要概括如下: 记录Compaction相关信息,使得Compaction过程能在需要的时候被触发: 维护SST文件索引信息及层次信息,为整个LevelDB的读.写.Compaction提供数据结构支持: 负责元信息数据的持久化,使得整个库可以从进程重启或机器宕机中恢复到正确的状态: 记录Log

Tair:一种分布式NOSQL存储引擎

Tair 存储引擎 tair 是阿里巴巴公司的一个开源项目,它是一个分布式的key/value结构数据的解决方案. tair 分为持久化和非持久化两种使用方式: 非持久化的 tair 可以看成是一个分布式缓存: 持久化的 tair 将数据存放于磁盘中,为了解决磁盘损坏导致数据丢失,tair 可以配置数据的备份数目.tair 自动将一份数据的不同备份放到不同的主机上,当有主机发生异常,无法正常提供服务的时候,其于的备份会继续提供服务. Tair的存储引擎有一个抽象层,只要满足存储引擎需要的接口,便

淘宝开源Key/Value结构数据存储系统Tair技术剖析

摘要: Tair的功能 Tair是一个Key/Value结构数据的解决方案,它默认支持基于内存和文件的两种存储方式,分别和我们通常所说的缓存和持久化存储对应. Tair除了普通Key/Value系统提供的功能,比如get.put.delete以及批量接口外,还有一些附加的实用功能,使得其有更广的适用场景,包括: Version支持> 原子计数器 Item支持 Tair是由淘宝网自主开发的Key/Value结构数据存储系统,在淘宝网有着大规模的应用.您在登录淘宝.查看商品详情页面或者在淘江湖和好友

也谈乐观并发控制(转)

add by zhj: 本文主要谈的是乐观并发控制,虽然乐观并发控制不太适用于并发写冲突很频繁的场景下,因为这样会导致事务回滚,需要用户重试retry, 但是如果不用乐观并发控制的话,貌似也没有其它什么好的办法了,悲观锁并不能解决更新丢失的问题,比如本文中的例子,我们也可以想想Git 遇到这种情况时是怎么处理的,其实Git也会像本文一样处理.为什么说悲观锁也不能完全解决更新丢失的问题呢?我们看下面的例子,两个用户 张三,李四,他们两人可以更新同一条数据库记录,假设记录为(sex,age) = (

ENode框架Conference案例转载

ENode框架Conference案例分析系列之 - Quick Start 前言 前一篇文章介绍了Conference案例的架构设计,本篇文章开始介绍Conference案例的代码实现.由于代码比较多,一开始就全部介绍所有细节,估计很多人接受不了,也理解不了.所以,我先进行一次QuickStart的介绍,即选取某个简单典型的场景从前到后过一下每个环节.这样大家就能够快速对代码的重要关键环节有大概的理解.另外,我现在正在做ENode的官网,到时会像axon framework一样,介绍ENode

Tair是由淘宝网自主开发的Key/Value结构数据存储系统

Tair是由淘宝网自主开发的Key/Value结构数据存储系统,在淘宝网有着大规模的应用.您在登录淘宝.查看商品详情页面或者在淘江湖和好友"捣浆糊"的时候,都在直接或间接地和Tair交互. Tair的功能 Tair是一个Key/Value结构数据的解决方案,它默认支持基于内存和文件的两种存储方式,分别和我们通常所说的缓存和持久化存储对应. Tair除了普通Key/Value系统提供的功能,比如get.put.delete以及批量接口外,还有一些附加的实用功能,使得其有更广的适用场景,包

淘宝网自主开发数据存储系统

Tair是由淘宝网自主开发的Key/Value结构数据存储系统,在淘宝网有着大规模的应用.您在登录淘宝.查看商品详情页面或者在淘江湖和好友"捣浆糊"的时候,都在直接或间接地和Tair交互. Tair的功能 Tair是一个Key/Value结构数据的解决方案,它默认支持基于内存和文件的两种存储方式,分别和我们通常所说的缓存和持久化存储对应. Tair除了普通Key/Value系统提供的功能,比如get.put.delete以及批量接口外,还有一些附加的实用功能,使得其有更广的适用场景,包

ENode框架Conference案例分析系列之 - Quick Start

前言 前一篇文章介绍了Conference案例的架构设计,本篇文章开始介绍Conference案例的代码实现.由于代码比较多,一开始就全部介绍所有细节,估计很多人接受不了,也理解不了.所以,我先进行一次QuickStart的介绍,即选取某个简单典型的场景从前到后过一下每个环节.这样大家就能够快速对代码的重要关键环节有大概的理解.另外,我现在正在做ENode的官网,到时会像axon framework一样,介绍ENode框架本身.使用场景.性能数据.案例,以及论坛社区等功能: 本文打算选择Conf