levelDB 的版本控制
levelDB为什么需要版本控制
在一个使用levelDB的服务中,必然存在多个线程同时访问数据库的情况。例如,如果正好有thread2正在访问sstable4。与此同时,thread1在写数据时,发生了compaction,level0中的sstable1需要与level1中的sstable4进行compaction,这是thread1应该基于当前version,去产出一个新的version,以免线程之间的互相影响,保证数据的一致性。
level0: sstable1 sstable2
level1: sstable3 sstable4 sstable5
FileMetaDatas类
FileMetaDatas是对每个本次磁盘文件sstable的描述,是磁盘文件和version的桥梁。而 version是词表文件和数据之间的桥梁。
之前我们在LevelDB-总体介绍 中提到一个疑问,levelDB是将磁盘文件以层的结构存在,那么哪里维护这个层结构呢,其实就是在Version类中。
FileMetaDatas 中记录着文件的名字,文件所占的字节大小,文件的最小InternalKey和最大的InternalKey以及有多少线程正在使用该文件。
版本控制
levelDB中,版本控制涉及的类有Version 、 VersionSet 、VersionEdit 以及 Build,他们之间的关系如下:
VersionSet 中维护一个双向链表,其中每一个节点为Version对象。在version类中,维护着一系列指向FileMetaData的指针。levelDB中任何对磁盘sstable的修改/增加/删除,首先将变更生成一个 VersionEdit 对象,然后基于Build类,生成一个新的Version,存储到VersionSet维护的双向链表中。
即 Version + VersionEdit = New Version,其中 += 操作由Build类完成。
首先明确一点:什么时候会发生版本变更:
就是在发生compaction的时候,在levelDB中compaction的类型有:
- minor compaction : immutable 到 sstable,不一定一定会落盘到level0层;
- size compaction :
- seek compaction:
本文着重讲解一下minor compaction。具体如何实现呢,下面简化LevelDB中的源码,大致讲一下这个过程:
// 首先初始化一个文件元信息
FileMetaData meta;
// memtbale的迭代器
Iterator* iter = mem->NewIterator();
// 真正地向磁盘写一个sstable,这时候,并不知道该sstable应该放到第几层
s = BuildTable(dbname_, env_, options_, table_cache_, iter, &meta);
// base 指向当前version
Version* base = ....;
// 判断刚才写入的sstable应该放到第几层中
level = base->PickLevelForMemTableOutput(min_user_key, max_user_key);
// 将SSTable文件序列号对应的层,Key等信息写入manifest中
edit->AddFile(level, meta.number, meta.file_size, meta.smallest,meta.largest);
Version* v = new Version(this);{Builder builder(this, current_);builder.Apply(edit);builder.SaveTo(v);}
VersionEdit 类
minor compaction的第一步就是将immutable转换成VersionEdit。
VersionEdit类是保存变更的类。其中有两个特别重要的类成员deleted_files_, new_files_。
new_files_ 记录新增sstable磁盘文件,采用pair结构记录,第一个参数记录的level,即放在第几层中,第二个记录的文件的元信息;
deleted_files_ 记录的是删除 第几层的那个sstable。
typedef std::set<std::pair<int, uint64_t>> DeletedFileSet;// 和VersionSet里面的compact_pointers_相同std::vector<std::pair<int, InternalKey>> compact_pointers_;// 有哪些文件被删除,就是Version里哪些SSTable被删除DeletedFileSet deleted_files_;// 有哪些文件被增加,pair的第一个参数是Level,第二个参数是文件的元信息std::vector<std::pair<int, FileMetaData>> new_files_;
Version类
Version其实很好理解,就是记录着当前版本有那些文件,并且记录这些文件的层级结构。
class Version {public:
... Status Get(const ReadOptions&, const LookupKey& key, std::string* val,GetStats* stats);bool UpdateStats(const GetStats& stats);bool RecordReadSample(Slice key);// Reference count management (so Versions do not disappear out from// under live iterators)void Ref();void Unref();
... ... // Return the level at which we should place a new memtable compaction// result that covers the range [smallest_user_key,largest_user_key]./* 该函数用来选择 需要将从MemTable dump出的sstable file放入第几层*/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;explicit Version(VersionSet* vset): vset_(vset),next_(this),prev_(this),refs_(0),file_to_compact_(nullptr),file_to_compact_level_(-1),compaction_score_(-1),compaction_level_(-1) {}Version(const Version&) = delete;Version& operator=(const Version&) = delete;~Version();Iterator* NewConcatenatingIterator(const ReadOptions&, int level) const;void ForEachOverlapping(Slice user_key, Slice internal_key, void* arg,bool (*func)(void*, int, FileMetaData*));/*所有的version都属于一个集合即Version Set*/VersionSet* vset_; // VersionSet to which this Version belongsVersion* next_; // Next version in linked listVersion* prev_; // Previous version in linked listint refs_; // Number of live refs to this version// SSTable的信息,每一项代表相应Level的SSTable信息// 除了Level 0外,每个Level里的文件都是按照最小键的顺序排列的,并且没有重叠// 通过这个数据项,搜索SSTable时,就可以从Level 0开始搜索std::vector<FileMetaData*> files_[config::kNumLevels]; // Next file to compact based on seek stats.FileMetaData* file_to_compact_; // 哪个文件需要 seek compactionint file_to_compact_level_; // seek compaction 发生在哪一层中double compaction_score_; // 所有level层中 最大的compaction coreint compaction_level_; // size compaction 发生在哪一层中
};
/**
判断以smallest_user_key为最小值,larget_user_key为最大只的sstable应该放到当前version的第几层
**/
int Version::PickLevelForMemTableOutput(const Slice& smallest_user_key,const Slice& largest_user_key) {int level = 0;if (!OverlapInLevel(0, &smallest_user_key, &largest_user_key)) {// Push to next level if there is no overlap in next level,// and the #bytes overlapping in the level after that are limited.InternalKey start(smallest_user_key, kMaxSequenceNumber, kValueTypeForSeek);InternalKey limit(largest_user_key, 0, static_cast<ValueType>(0));std::vector<FileMetaData*> overlaps;while (level < config::kMaxMemCompactLevel) {if (OverlapInLevel(level + 1, &smallest_user_key, &largest_user_key)) {break;}if (level + 2 < config::kNumLevels) {// Check that file does not overlap too many grandparent bytes.GetOverlappingInputs(level + 2, &start, &limit, &overlaps);const int64_t sum = TotalFileSize(overlaps);if (sum > MaxGrandParentOverlapBytes(vset_->options_)) {break;}}level++;}}return level;
}
疑问:
为什么在Version类中,将VersionSet设置为友元,设置一个成员变量VersionSet* vset_?
我的理解: 所有一系列的Version构成了VersionSet,那么一个Version归属于那个VersionSet呢?就用这个成员对象进行记录。并且在Version的成员函数中需要使用VersionSet的私有变量,所以将VersionSet设置为友元。
下面重点讲解一下 Version::PickLevelForMemTableOutput 函数。在 immutable落盘sstable时,即 minor compaction 时使用。作用是判断新增sstable需要放在哪一层中。该函数的流程图如下所示:
首先读者要知道两点:
- 在levelDB中,level0的数据要比level1中的数据新,level1中的数据 要比level2中的数据新;
- level0中的sstable之间,key是可以有交集的,level1 … leveln中的sstable之间key之间是没有交集的。
回到 PickLevelForMemTableOutput 函数,下面详细说一下具体流程:
- 首先判断新增sstable是否与level0有交集,如果有交集,直接插入到level0中。
- 如果新增sstable与level1 没有交集,并且与level2中有交集的sstable个数小于阈值,则可以插入到level1中;
- 如果新增sstable与level2 没有交集,并且与level3中有交集的sstable个数小于阈值,则插入到level2中;
从流程图中可以看出来,新增sstable最多就插入到level2中;提问1:拿level1来举例子,为什么不光要判断是否与level1有交集,还要判断与level2的交集文件个数呢?
答案:首先对于>level1的层,层中的sstable之间是无交集的。所以第一个判断很好理解;第二个判断是问了降低size compaction的成本;假设new sstable插入到level1中,但与level2中的交集有10个(很多了)。当new sstable需要进行size compaction时,那么compaction的文件就可能达到>15个,那么本次size compaction的成本就相当高了。
VersionSet类
到这里,我们已经基于immutable生成了VersionEdit对象,并生成了 new version。那么现在,我们需要将new version应用起来。即让levelDB感知到新增的version。
这里只介绍添加VersionEdit对象的函数LogAndApply。 VersionSet类的具体细节后面再加,因为现在我也不太清楚,嘻嘻嘻
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 = new Version(this);{Builder builder(this, current_);builder.Apply(edit);builder.SaveTo(v);}// 计算版本 v 中,compaction score // 到这里,新的version已经构造完毕Finalize(v);// Initialize new descriptor log file if necessary by creating// a temporary file that contains a snapshot of the current version.std::string new_manifest_file;Status s;if (descriptor_log_ == nullptr) {// No reason to unlock *mu here since we only hit this path in the// first call to LogAndApply (when opening the database).assert(descriptor_file_ == nullptr);new_manifest_file = DescriptorFileName(dbname_, manifest_file_number_);edit->SetNextFile(next_file_number_);s = env_->NewWritableFile(new_manifest_file, &descriptor_file_);if (s.ok()) {descriptor_log_ = new log::Writer(descriptor_file_);s = WriteSnapshot(descriptor_log_);}}// Unlock during expensive MANIFEST log write // 日志操作{... }// Install the new versionif (s.ok()) {AppendVersion(v);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_ = nullptr;descriptor_file_ = nullptr;env_->RemoveFile(new_manifest_file);}}return s;
}// A helper class so we can efficiently apply a whole sequence
// of edits to a particular state without creating intermediate
// Versions that contain full copies of the intermediate state.
class VersionSet::Builder {private:// Helper to sort by v->files_[file_number].smalleststruct BySmallestKey {const InternalKeyComparator* internal_comparator;bool operator()(FileMetaData* f1, FileMetaData* f2) const {int r = internal_comparator->Compare(f1->smallest, f2->smallest);if (r != 0) {return (r < 0);} else {// Break ties by file numberreturn (f1->number < f2->number);}}};typedef std::set<FileMetaData*, BySmallestKey> FileSet;struct LevelState {std::set<uint64_t> deleted_files;FileSet* added_files;};VersionSet* vset_;Version* base_;LevelState levels_[config::kNumLevels];public:// Initialize a builder with the files from *base and other info from *vset// 根据VersionSet,构造internal的比较函数;// 构造磁盘level结构Builder(VersionSet* vset, Version* base) : vset_(vset), base_(base) {base_->Ref();BySmallestKey cmp;cmp.internal_comparator = &vset_->icmp_;for (int level = 0; level < config::kNumLevels; level++) {levels_[level].added_files = new FileSet(cmp);}}~Builder() {for (int level = 0; level < config::kNumLevels; level++) {const FileSet* added = levels_[level].added_files;std::vector<FileMetaData*> to_unref;to_unref.reserve(added->size());for (FileSet::const_iterator it = added->begin(); it != added->end();++it) {to_unref.push_back(*it);}delete added;for (uint32_t i = 0; i < to_unref.size(); i++) {FileMetaData* f = to_unref[i];f->refs--;if (f->refs <= 0) {delete f;}}}base_->Unref();}// Apply all of the edits in *edit to the current state.// 应用VersionEdit对象中的所有记录,填充Builder构造函数中 构造出来的层结构void Apply(VersionEdit* edit) {// Update compaction pointers// 这块不知道干嘛用的,需要再看 TODOfor (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();}// Delete filesfor (const auto& deleted_file_set_kvp : edit->deleted_files_) {const int level = deleted_file_set_kvp.first;const uint64_t number = deleted_file_set_kvp.second;levels_[level].deleted_files.insert(number);}// Add new filesfor (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;// We arrange to automatically compact this file after// a 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.f->allowed_seeks = static_cast<int>((f->file_size / 16384U));if (f->allowed_seeks < 100) f->allowed_seeks = 100;levels_[level].deleted_files.erase(f->number);levels_[level].added_files->insert(f);}}// Save the current state in *v.void SaveTo(Version* v) {BySmallestKey cmp;cmp.internal_comparator = &vset_->icmp_;for (int level = 0; level < config::kNumLevels; level++) {// Merge the set of added files with the set of pre-existing files.// Drop any deleted files. Store the result in *v.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_files = levels_[level].added_files;v->files_[level].reserve(base_files.size() + added_files->size());for (const auto& added_file : *added_files) {// Add all smaller files listed in base_for (std::vector<FileMetaData*>::const_iterator bpos =std::upper_bound(base_iter, base_end, added_file, cmp);base_iter != bpos; ++base_iter) {MaybeAddFile(v, level, *base_iter);}MaybeAddFile(v, level, added_file);}// Add remaining base filesfor (; base_iter != base_end; ++base_iter) {MaybeAddFile(v, level, *base_iter);}#ifndef NDEBUG// Make sure there is no overlap in levels > 0if (level > 0) {for (uint32_t i = 1; i < v->files_[level].size(); i++) {const InternalKey& prev_end = v->files_[level][i - 1]->largest;const InternalKey& this_begin = v->files_[level][i]->smallest;if (vset_->icmp_.Compare(prev_end, this_begin) >= 0) {std::fprintf(stderr, "overlapping ranges in same level %s vs. %s\n",prev_end.DebugString().c_str(),this_begin.DebugString().c_str());std::abort();}}}
#endif}}void MaybeAddFile(Version* v, int level, FileMetaData* f) {if (levels_[level].deleted_files.count(f->number) > 0) {// File is deleted: do nothing} else {std::vector<FileMetaData*>* files = &v->files_[level];if (level > 0 && !files->empty()) {// Must not overlapassert(vset_->icmp_.Compare((*files)[files->size() - 1]->largest,f->smallest) < 0);}f->refs++;files->push_back(f);}}
};
提问的时间到了。
问题1: 为什么Builder类要设计在VersionSet类内?
答案:Builder是在VersionSet的private中声明的,Builder就作用就是将一些函数再次封装,使得整体代码,看起来更加整洁。这些函数只会在VersionSet类函数中使用。
levelDB 的版本控制相关推荐
- LevelDB原理及应用
LevelDB LevelDB之概览 LevelDB是Google传奇工程师Jeff Dean和Sanjay Ghemawat开源的KV存储引擎. 了解原理之前首先要用起来,下面动手实现个例子:安装 ...
- 半小时学会LevelDB原理及应用
LevelDB LevelDB之概览 LevelDB是Google传奇工程师Jeff Dean和Sanjay Ghemawat开源的KV存储引擎. 了解原理之前首先要用起来,下面动手实现个例子:安装调 ...
- Leveldb源码分析--15
9 LevelDB框架之2 9.4 版本控制 当执行一次compaction后,Leveldb将在当前版本基础上创建一个新版本,当前版本就变成了历史版本.还有,如果你创建了一个Iterator,那么该 ...
- LevelDB库简介
LevelDB库简介 一.LevelDB入门 LevelDB是Google开源的持久化KV单机数据库,具有很高的随机写,顺序读/写性能,但是随机读的性能很一般,也就是说,LevelDB很适合应用在查询 ...
- leveldb 原理解析
目录 概览 Features 整体结构 Memtable Immutable Memtable SSTable 文件(SST) SSTable的物理结构 Block 物理结构 节省 key 占用空间 ...
- levelDB学习记录
文章目录 levelDB学习记录 博客 LevelDB源码分析 1.起步 2.Visual Studio Code 3.基本思路 LevelDB四个接口 磁盘的特点 状态机 状态机的四个概念 初步设计 ...
- 从JoinBatchGroup 代码细节 来看Rocksdb的相比于leveldb的写入优势
文章目录 1. Rocksdb写入模型 2. LevelDB写入的优化点 3. Rocksdb 的优化 1. Busy Loop 2. Short Wait -- SOMETIMES busy Loo ...
- 软件构造 第二章 第一节 软件生命周期和版本控制
软件构造第二章 第一节 软件生命周期和版本控制 基本内容 Software Development Lifecycle (SDLC) Traditional software process mode ...
- 数据集cifar10到Caffe支持的lmdb/leveldb转换的实现
在 http://blog.csdn.net/fengbingchun/article/details/53560637 对数据集cifar10进行过介绍,它是一个普通的物体识别数据集.为了使用Caf ...
最新文章
- 谷歌被指骗取人脸数据:部分获取方法可疑
- ABAP中的F4帮助怎么用?
- 实验二 二叉树的操作与实现
- Java关键字—instanceof
- Java字符编码介绍
- java自学路线图_JAVA自学路线图
- IPC--进程间通信四(信号量)
- python有趣小程序-第一个有趣的python小程序
- android 输入法出现挤压屏幕、android输入键盘覆盖了屏幕控件的解决办法
- 上传本地项目到gitee_使用git将本地代码上传到gitee远程仓库
- [C#] DBNull、Null和String.Empty的区别
- u盘pe安装深度linux系统教程,深度U盘装机大师_深度u盘启动盘安装深度系统
- android srgb模式,一加3固件官方更新:加入屏幕边缘防误触和sRGB显示模式功能
- 预测:深度学习未来的6种可能
- css3弹性盒子居中总结1
- 流数据分析之地理围栏应用
- 新机如何把机械硬盘中的系统克隆到固态硬盘
- 分布式调度问题及解决方案
- 知物由学|游戏开发者如何从容应对Unity手游风险?
- 电子邮箱怎么注册,公司用什么电子邮箱好
热门文章
- 寻找第n个默尼森数。
- 一次难忘的bug处理经历
- 为什么我的话总没说服力
- SDNU_ACM_ICPC_2020_Winter_Practice_4th(补题2020.2.2)
- python最小二乘法拟合直线_Python 实现最小二乘法拟合直线
- 耐威迪参加第十一届中国优秀数据中心峰会并做主题演讲
- MySQL批量更新数据总结
- Android拖拽排序控件DragGridView
- 网络安全学习笔记——第五天 防火墙功能特性
- 产品发布 | 京东云推出【云搜索】服务,海量数据全文检索从未如此简单