【数据成员介绍】
compaction_level_:下一个需要做compact的层级;
compaction_score_:比分,如果比分小于1,就代表没有那么紧急去需要做compact;
那么比分的规则是什么呢?
void VersionSet::Finalize(Version* v) {
int best_level = -1;
double best_score = -1;
for (int level = 0; levle < config::kNumberLevels - 1; level++) {
double score;
if (levle == 0) {
score = v->files_[level].size() / static_cast<double>(config::kL0_CompactionTrigger);
} else {
const uint64_t level_bytes = TotalfileSize(v->files_[level]);
score = static_cast<double>(level_byles) / MaxBylesForLevel(level);
}
if (score > best_score) {
best_level = level;
best_score = score;
}
}
v->compaction_levle_ = best_level;
v->compaction_score = best_score;
}
从这个函数就可以看出,挑选出比分最高的层,就是下一个最需要做compact的层。从函数中可以看出,level 0的比分规则和其他层不一样,level 0是按照已有的sst文件数/一个限制值(这里是4),其它层是按照文件大小来比的,现有文件大小/该层文件大小限制。每层的文件大小限制versionset里面有讲,这里就不啰嗦了。为什么采用不同的标准来比分呢?有两个原因:一方面当write-buffer很大的时候,不用很频繁的对level 0做compact,另一方面,当读取的时候,文件太多会导致merge的成本变高,所以level 0的文件数要限制。
file_to_compact_;基于seek stats来决定下一个要compact的文件
file_to_compact_level_;file_to_compact_所在的层级;
那么挑选下一个进行compact的规则又是什么呢?这个放在后面说。
files_:该版本每个level的sst文件的FileMetaData
next_:指向下一个版本的指针;
prev_:指向上一个版本的指针;
refs_:引用计数;
vset_:指向一个versionset
【方法成员介绍】
Status Version::Get(const ReadOptions& options,const LookupKey& k,std::string* value,GetStats* stats)
作用:获得要查询key的值
Get方法,是在取数据的时候调用,如果能通过MemTable(Immutable Memtable、Memtable)找到,就不会调这个。下面分析下这个方法的逻辑。
step1:通过files_,循环每一层,根据leveldb的特性,如果这个key在上层能找到,就不用继续往下层找了,因为上层的数据一定比下层更新。
step2:level 0比较特殊,它的sst文件之间的key范围是存在交叠的,所以首先要找出包含key的所有sst,然后将其排序,最新(文件编号越大越新)的放前面;其他level(大于0)存在一个特性,就是sst文件之间的key range是没有交叠的,这么设计的目的就是为了现在,在读取的时候不用处理交叠的情况,提高读的效率,所以只要找出一个包含要查找key的sst文件即可,就可以结束在该层的查找了。
step3:在sst中查找,先在table cache中找(table cache中采用的是LRU算法,后面详细介绍),找不到再读文件。
void Version::GetOverlappingInputs(int level,const InternalKey* begin,const InternalKey* end,std::vector<FileMetaData*>* inputs)
作用:获得在某一个level中和key range[begin,end]有交叠的所有文件,结果保存在inputs,如果是level 0,并且有新增的文件,会扩大这个range,重新搜索。
bool Version::OverlapInLevel(int level,const Slice* smallest_user_key,const Slice* largest_user_key)
作用:判断指定范围的key range[smallest_user_key,largest_user_key]在指定的level是否有交叠。
int Version::PickLevelForMemTableOutput(const Slice& smallest_user_key,const Slice& largest_user_key)
作用:为memTable的dump到sst挑选出一个合适的层。很多人以为数据是从memtable直接dump到level 0 的,其实不然。让我先分析下这个函数的逻辑吧。
step1:如果range[smallest_user_key,largest_user_key]和level 0有交叠,那么就选level 0,这个时候判断是否有交叠的函数(bool Version::OverlapInLevel)就派上用场了。
step2:当level < config::kMaxMemCompactLevel情况下进行循环,如果和level + 1层没有交叠,和level + 2层交叠的sst文件大小没有超过kMaxGrandParentOverlapBytes,那么就往下一层进行选择;config::kMaxMemCompactLevel=2,也就是说挑选出来的level要么是level 0层,要么是level 1层。
这么做的目的,我觉得是一个折中的策略,一方面不直接放level 0,减少level 0的文件数,就可以提高读取的效率,还可以减少compact;另一方面,kMaxMemCompactLevel=2,如果太大,在读取的时候,会增加seek的次数。