Version之后就是VersionSet,它并不是Version的简单集合,还肩负了不少的处理逻辑。这里的分析不涉及到compaction相关的部分,这部分会单独分析。包括log等各种编号计数器,compaction点的管理等等。
Version* current() const { return current_; } //返回current version // 当前的MANIFEST文件号 uint64_t ManifestFileNumber() const { return manifest_file_number_; } uint64_t NewFileNumber() { return next_file_number_++; } // 分配并返回新的文件编号 uint64_t LogNumber() const { return log_number_; } // 返回当前log文件编号 // 返回正在compact的log文件编号,如果没有返回0 uint64_t PrevLogNumber() const { return prev_log_number_; } // 获取、设置last sequence,set时不能后退 uint64_t LastSequence() const { return last_sequence_; } void SetLastSequence(uint64_t s) { assert(s >=last_sequence_); last_sequence_ = s; } // 返回指定level中所有sstable文件大小的和 int64_t NumLevelBytes(int level) const; // 返回指定level的文件个数 int NumLevelFiles(int level) const; // 重用@file_number,限制很严格:@file_number必须是最后分配的那个 // 要求: @file_number是NewFileNumber()返回的. void ReuseFileNumber(uint64_t file_number) { if (next_file_number_ ==file_number + 1) next_file_number_ = file_number; } // 对于所有level>0,遍历文件,找到和下一层文件的重叠数据的最大值(in bytes) // 这个就是Version:: GetOverlappingInputs()函数的简单应用 int64_t MaxNextLevelOverlappingBytes(); // 获取函数,把所有version的所有level的文件加入到@live中 void AddLiveFiles(std::set<uint64_t>* live) // 返回一个可读的单行信息——每个level的文件数,保存在*scratch中 struct LevelSummaryStorage {char buffer[100]; }; const char* LevelSummary(LevelSummaryStorage* scratch) const;
下面就来分析这两个接口Recover、LogAndApply以及ApproximateOffsetOf。
Builder的vset_与base_都是调用者传入的,此外它还为FileMetaData定义了一个比较类BySmallestKey,首先依照文件的min key,小的在前;如果min key相等则file number小的在前。
typedefstd::set<FileMetaData*, BySmallestKey> FileSet; struct LevelState { // 这个是记录添加和删除的文件 std::set<uint64_t>deleted_files; FileSet* added_files; // 保证添加文件的顺序是有效定义的 }; VersionSet* vset_; Version* base_; LevelStatelevels_[config::kNumLevels]; // 其接口有3个: void Apply(VersionEdit* edit) void SaveTo(Version* v) void MaybeAddFile(Version* v, int level, FileMetaData* f)构造函数执行简单的初始化操作,在析构时,遍历检查LevelState::added_files,如果文件引用计数为0,则删除文件。
for (size_t i = 0; i <edit->new_files_.size(); i++) { const int level =edit->new_files_[i].first; FileMetaData* f = newFileMetaData(edit->new_files_[i].second); f->refs = 1; f->allowed_seeks = (f->file_size /16384); // 16KB-见下面 if (f->allowed_seeks <100) f->allowed_seeks = 100; levels_[level].deleted_files.erase(f->number); // 以防万一 levels_[level].added_files->insert(f); }值allowed_seeks事关compaction的优化,其计算依据如下,首先假设:
conststd::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 =levels_[level].added_files; v->files_[level].reserve(base_files.size()+ added->size()); for (FileSet::const_iteratoradded_iter = added->begin(); added_iter !=added->end(); ++added_iter) { //加入base_中小于added_iter的那些文件 for(std::vector<FileMetaData*>::const_iterator bpos = std::upper_bound(base_iter,base_end, *added_iter, cmp); base_iter != bpos;++base_iter) { // base_iter逐次向后移到 MaybeAddFile(v, level,*base_iter); } MaybeAddFile(v, level,*added_iter); // 加入added_iter } // 添加base_剩余的那些文件 for (; base_iter != base_end;++base_iter) MaybeAddFile(v, level, *base_iter);对象cmp就是前面定义的比较仿函数BySmallestKey对象。
对于VersionSet而言,Recover就是根据CURRENT指定的MANIFEST,读取db元信息。这是9.3介绍的Recovery流程的开始部分。
下面就来分析其具体逻辑。
S1 读取CURRENT文件,获得最新的MANIFEST文件名,根据文件名打开MANIFEST文件。CURRENT文件以\n结尾,读取后需要trim下。
std::string current; // MANIFEST文件名 ReadFileToString(env_, CurrentFileName(dbname_), ¤t) std::string dscname = dbname_ + "/" + current; SequentialFile* file; env_->NewSequentialFile(dscname, &file);
S2 读取MANIFEST内容,MANIFEST是以log的方式写入的,因此这里调用的是log::Reader来读取。然后调用VersionEdit::DecodeFrom,从内容解析出VersionEdit对象,并将VersionEdit记录的改动应用到versionset中。读取MANIFEST中的log number, prev log number, nextfile number, last sequence。
Builder builder(this, current_); while (reader.ReadRecord(&record, &scratch) && s.ok()) { VersionEdit edit; s = edit.DecodeFrom(record); if (s.ok())builder.Apply(&edit); if (edit.has_log_number_) { // log number, file number, …逐个判断 log_number =edit.log_number_; have_log_number = true; } … … }S3 将读取到的log number, prev log number标记为已使用。
if (s.ok()) { Version* v = newVersion(this); builder.SaveTo(v); // 安装恢复的version Finalize(v); AppendVersion(v); manifest_file_number_ =next_file; next_file_number_ = next_file+ 1; last_sequence_ = last_sequence; log_number_ = log_number; prev_log_number_ =prev_log_number; }Finalize(v)和AppendVersion(v)用来安装并使用version v,在AppendVersion函数中会将current version设置为v。下面就来分别分析这两个函数。