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中。