classVersion { private: 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
// List of files per level std::vector<FileMetaData*> files_[config::kNumLevels]; // Next file to compact based on seek stats. FileMetaData* file_to_compact_; int file_to_compact_level_;
// Level that should be compacted next and its compaction score. // Score < 1 means compaction is not strictly needed. These fields // are initialized by Finalize(). double compaction_score_; int compaction_level_; };
Version 通过引用计数的方法进行管理,当引用数为 0 时,会从双向链表中删除该节点,并销毁该 Version。每当需要使用 Version 时都会将引用计数加一,使用完毕会引用计数会减一。当压缩生成新的 Version 引用计数也会加一,并将旧 Version 的引用计数减一。之所以需要将不同版本的 Version 都保存下来是因为在多线程访问时,各个线程访问的是不同版本的结构,只有当某个版本没有被访问时才能将其删除。
classDiagram
class FileMetaData {
+ int refs
+ int allowed_seeks
+ uint64_t number
+ uint64_t file_size
+ InternalKey smallest
+ InternalKey largest
+ FileMetaData()
}
number 表示这个 SSTable 的文件编号,在 LevelDB 中以文件编号作为 SSTable 的文件名
Status Get(const ReadOptions&, const LookupKey& key, std::string* val, GetStats* stats);
// compaction may need to be triggered, false otherwise. boolUpdateStats(const GetStats& stats);
// Record a sample of bytes read at the specified internal key. // Samples are taken approximately once every config::kReadBytesPeriod // bytes. Returns true if a new compaction may need to be triggered. // REQUIRES: lock is held boolRecordReadSample(Slice key);
// Reference count management (so Versions do not disappear out from under live iterators) voidRef(); voidUnref();
voidGetOverlappingInputs( int level, const InternalKey* begin, // nullptr means before all keys const InternalKey* end, // nullptr means after all keys std::vector<FileMetaData*>* inputs);
// Return the level at which we should place a new memtable compaction // result that covers the range [smallest_user_key,largest_user_key]. intPickLevelForMemTableOutput(const Slice& smallest_user_key, const Slice& largest_user_key);
// 在原有 Version 的基础上构建新 VersionEdit Version* v = newVersion(this); { Builder builder(this, current_); builder.Apply(edit); builder.SaveTo(v); } 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). new_manifest_file = DescriptorFileName(dbname_, manifest_file_number_); s = env_->NewWritableFile(new_manifest_file, &descriptor_file_); descriptor_log_ = new log::Writer(descriptor_file_); s = WriteSnapshot(descriptor_log_); }
{ mu->Unlock(); // 写 manifest 文件 std::string record; edit->EncodeTo(&record); s = descriptor_log_->AddRecord(record); s = descriptor_file_->Sync();
// If we just created a new descriptor file, install it by writing a // new CURRENT file that points to it. if (s.ok() && !new_manifest_file.empty()) { s = SetCurrentFile(env_, dbname_, manifest_file_number_); } mu->Lock(); }
// Install the new version AppendVersion(v); // 将新 Version 添加到 VersionSet 中 log_number_ = edit->log_number_; prev_log_number_ = edit->prev_log_number_;
return s; }
Manifest
Version 是内存中的结构,为了让系统在关闭后能够恢复,还需要讲内存中的结构持久化到磁盘中,这就引入了 Manifest 文件。每当 Version 应用 VersionEdit 时,需要将 VersionEdit 中的内容写入到 Manifest 文件中,为此 VersionEdit 提供了EncodeTo(string) 方法对其自身编码。写入 Manifest 的数据包含如下信息
1 2 3 4 5 6 7 8 9 10 11
enumTag { kComparator = 1, // InternalKey comparator kLogNumber = 2, // 当前 log 文件编号 kNextFileNumber = 3, // 下一个文件编号 kLastSequence = 4, // 当前版本最后一个 SequenceNumber kCompactPointer = 5, // kDeletedFile = 6, // 该版本需要删除的文件元数据 kNewFile = 7, // 该版本需要添加的文件元数据 // 8 was used for large value refs kPrevLogNumber = 9// 前一个 Version Log 文件编号 };
为了能够在恢复时识别数据信息,需要将 tag 连同数据一起写进 Manifest 文件,每个 tag 时一个 varint32 的值,只占用 1 个字节。需要注意的是,需要新增的文件和需要删除的文件可能有多个,所以对于每一个新增或删除的文件,都需要写入 kDeletedFile 或 kNewFile tag。对于 kDeletedFile,需要写入 文件的 level 和 file number 信息;对于 kNewFile 不仅需要写入文件的 level 和 file number,还需要写入文件的 size、smallest key 和 largest key,这是为了恢复时能够将文件元信息恢复位 FileMetaData 结构。
在 DB 恢复后第一次修改系统结构时,会调用 WriteSnapshot 对当前状态进行一次快照,并写入新的 Manifest 文件中