VersionSet的作用是記錄各個版本,這些版本的組織形式是一個環形的雙向鏈表。
【數據成員介紹】
compact_pointer_:記錄每層下次做compact的開始的key;string型數組,index-->level, value-->internal key,
current_:指向當前版本;
dbname_:數據庫的名稱;
descriptor_file_:指向可寫的manifest文件;
descriptor_log_:指向log writer對象;
dummy_versions_:版本環形雙相鏈表的頭;
env_:指向與平臺相關的env;
icmp_:InternalKeyComparator,內部比較器;
last_sequence_:上一個序列號(快照就是靠它實現的);
log_number_:日誌文件編號;
manifest_file_number_:manifest文件編號;
next_file_number:下一個文件編號,文件編號計數器;
options_:應用配置項,調用者配置的哪些配置項;
prev_log_number_:
table_cache_:指向sst文件的cache對象;
【輔助方法介紹】
static double MaxBytesForLevel(int level)
作用:計算每層文件總大小閾值;但是level 0其實不是基於文件大小來的,而是基於文件個數,因爲level 0 sst文件的key range之間是允許有交疊,所以在查找的時候,如果文件太多,意味着增加了seek文件次數,讀的效率就會降低。
每層文件的總大小大小:
level0 : 10M
level1 : 10M
level2 : 100M
level3 : 1000M
level4 : 10000M
level5 : 100000M
level6 : 1000000M
static uint64_t MaxFileSizeForLevel(int level)
作用:每層最大的文件大小爲2M,(爲了減少每層的文件數,可以違反這個規則,因爲每個進程打開的文件數是有限制的,所以可以根據層級的不同來調整文件大小的限制,這個也算是一個改進點)
static int64_t TotalFileSize(const std::vector<FileMetaData*>& files)
作用:計算給定files的總文件大小;
int FindFile(const InternalKeyComparator& icmp,const std::vector<FileMetaData*>& files,const Slice& key)
作用:根據二分法查找key在files中的index,找不到將返回0或者files.size(),其中files對應的key range是有序的。
class VersionSet::Builder
作用:
levels_:將每層的增量記錄在這個數組裏,其中增加的文件是按照最小key排序保存的set;
struct VersionSet::Builder::BySmallestKey:
作用:幫助將version->files_按照v->files_[file_number].smallest去排序,所以每層(level > 0)的version->files_是有序的,這也是爲什麼FindFile方法可以採用二分法的原因。
void VersionSet::Builder::Apply(VersionEdit* edit)
作用: 將增量暫時保存在files_
step1:將compaction pointers直接更新到vset_->compact_pointer_[level];
step2:將增量中刪除的文件加入levels_[level].deleted_files中;
step3:將增量中增加的文件加入levels_[level].added_files中;此時added_files是有序的,這個依靠set來保證的;
此時新增的文件f的allowed_seeks賦值規則爲
f->allowed_seeks = (f->file_size / 16384);
if (f->allowed_seeks < 100) f->allowed_seeks = 100;
allowed_seeks <= 0是,說明就需要做compact了,原因如下:
一次seek需要10ms;
讀或寫1M數據需要10s(100M/s);
compact 1M數據會產生25MIO(1M讀從這個level;10-12M讀從寫一個level;寫10-12M從下一個level);
那麼一次seek和大概40KB的compact耗時差不多;那麼這裏的保守處理是,一次seek等價於16K的數據,但同時爲了保護compact太過頻繁,所以限制allowed_seeks至少爲100;
void VersionSet::Builder::SaveTo(Version* v)
作用: 將增量和當前版本merge成一個新的版本;因爲增量中新增的文件key range是有序的,而當前版本也是有序的,所以主要是將這兩個有序的list進行merge;
727 for (FileSet::const_iterator added_iter = added->begin();
728 added_iter != added->end();
729 ++added_iter) {
730
731 for (std::vector<FileMetaData*>::const_iterator bpos
732 = std::upper_bound(base_iter, base_end, *added_iter, cmp);//cmp 是通過f1->smallest來比較的
733 base_iter != bpos;
734 ++base_iter) {
// 如果added_iter比base_iter大,那麼就把base_iter加到新版本中
735 MaybeAddFile(v, level, *base_iter);
736 }
737 //如果added_iter比base_iter小,那麼就把added_iter加到新版本中
738 MaybeAddFile(v, level, *added_iter);
739 }
740
741 // 把當前版本中剩下的加到新版本中
742 for (; base_iter != base_end; ++base_iter) {
743 MaybeAddFile(v, level, *base_iter);
744 }
void MaybeAddFile(Version* v, int level, FileMetaData* f)
作用:也許增加文件到新版本的指定level,之所以是也許,如果是需要刪除的文件,就不會加了;
【方法成員介紹】
VersionSet::VersionSet(const std::string& dbname,const Options* options,TableCache* table_cache,const InternalKeyComparator* cmp)
作用:構造函數,next_file_number_(2),next_file_number_初始值是2,而不是1,因爲1是留給manifest_file_number_;
void VersionSet::AppendVersion(Version* v)
作用:指定新版本爲當前版本,並將新版本加到版本的雙向鏈表中。
Status VersionSet::LogAndApply(VersionEdit* edit, port::Mutex* mu)
作用:記錄當前版本和增量到磁盤中,應用增量到當前版本;
step1:將當前版本、增量merge到一個新版本;
step2:將當前版本的快照記錄在新的manifest文件中;保存前先用edit->EncodeTo進行編碼;
step3:將增量信息保存在新的manifest文件中;保存前先用edit->EncodeTo進行編碼;
step4:CURRENT指向新的manifest文件;
step5:當新版本設置爲當前版本,並加到版本雙向鏈表中;
這裏之所以將版本信息分批存,是爲了recover的時候,讀取起來耗費內存少;
Status VersionSet::Recover()
作用:將磁盤中的版本信息恢復到內存中;
step1:通過CURRENT找到menifest文件;
step2:讀取menifest文件,將內容解碼成VersionEdit對象;
step3:調用builder.Apply(&edit)應用增量;
step4:調用builder.SaveTo(v)將增量和當前版本merge到新版本;
step5:調用AppendVersion(v)追加新版本;
step6:更新文件計數器;
1023 manifest_file_number_ = next_file;
1024 next_file_number_ = next_file + 1;
1025 last_sequence_ = last_sequence;
1026 log_number_ = log_number;
1027 prev_log_number_ = prev_log_number;
Status VersionSet::WriteSnapshot(log::Writer* log)
作用:記錄下當前版本;
step1:新建一個新的VersionEdit對象edit;
step2:設置比較器;
step3:設置compaction pointers;
step4:添加當前版本的文件;
step5:用edit->EncodeTo進行編碼保存;
bool VersionSet::ManifestContains(const std::string& record) const
作用:看最新的manifest文件是否包含某條記錄
Iterator* VersionSet::MakeInputIterator(Compaction* c)
作用:將正在compaction的文件,變成一個迭代器。
void VersionSet::SetupOtherInputs(Compaction* c)
作用:設置下一個compact_pointer_、c->grandparents_、c->inputs_[0]、c->inputs_[1]信息。
step1:從c->inputs_[0]中得到key範圍,然後通過這個範圍,得到與c->level() + 1相交疊的文件c->inputs_[1];
step2:通過c->inputs_[0]和 c->inputs_[1]得到一個新的key範圍[all_start,all_limit];
step3:如果c->inputs_[1]不爲空,通過current_->GetOverlappingInputs(level, &all_start, &all_limit, &expanded0)得到擴展範圍expanded0,如果expanded0的文件大小+c->inputs_[1]的文件大小 < kExpandedCompactionByteSizeLimit;通過GetRange(expanded0, &new_start, &new_limit)得到新的key 範圍[ new_start, new_limit],通過
current_->GetOverlappingInputs(level+1, &new_start, &new_limit, &expanded1)得到expanded1,如果expanded1的文件個數和c->inputs_[1]的文件個數一樣,就將c->inputs_[0]=expanded0,c->inputs_[1]=expanded1;通過GetRange2(c->inputs_[0], c->inputs_[1], &all_start, &all_limit)計算出新的key範圍[all_start,all_limit];
step4:通過current_->GetOverlappingInputs(level + 2, &all_start, &all_limit,&c->grandparents_)得到c->grandparents_
step5:compact_pointer_[level] = largest.Encode().ToString(); c->edit_.SetCompactPointer(level, largest);從這裏可以看出,compact_pointer_記錄的是結束點的位置,這麼着急設置,而不等待VersionEdit去apply,是爲了,如果compaction失敗的話,下次可以compaction不同範圍的key,也就是說本次不管成功或失敗,先設置這些點,萬一失敗了,可以繼續之後的點,不用卡在這,一直死搞,提高效率。
其中step3的步驟比較複雜,其實就是在保證c->inputs_[1]不增加的情況下,儘量多處理一點c->inputs_[0];說白一點,就是,反正c->inputs_[1]要處理這麼些文件,還不如把與之交疊的,最大範圍key的文件集c->inputs_[0]給處理了,免得下次還得再處理這個範圍的key,當然前提是處理的大小在kExpandedCompactionByteSizeLimit範圍內。
Compaction* VersionSet::PickCompaction()
作用:根據兩種觸發方式來生成Compaction對象;
根據每層文件的大小比分觸發:
如果比分大於等於1,優先採用這種方式,根據current_->compaction_level_得到level,然後c = new Compaction(level);
找到這層中文件,文件key的最大範圍比compact_pointer_[level]大的,並將其放入c->inputs_[0],如果一個都沒找到,就將 這層的第一個文件放進去;
這個地方有個問題,爲什麼拿key範圍的最大值和compact_pointer_[level],比,而不是拿最小值?因爲level 0文件之間有交 疊,如果最小值比compact_pointer_[level]都大,那麼可能漏掉最小值比compact_pointer_[level]小的文件。
根據文件seek次數觸發:
根據current_->file_to_compact_level_得到level,然後c = new Compaction(level);再將current_->file_to_compact_加入
c->inputs_[0],
通過這兩種方式中的一種,如果c->inputs_[0]裏面有文件,並且level=0的話,還需要調GetRange(c->inputs_[0], &smallest, &largest);current_->GetOverlappingInputs(0, &smallest, &largest, &c->inputs_[0]);重新取c->inputs_[0],因爲level 0的文件是有交疊的。
接下來就是調 SetupOtherInputs(c);來設置c的其他成員數據,上一個方法已經講過了。
Compaction* VersionSet::CompactRange(int level,const InternalKey* begin,const InternalKey* end)
作用:指定層級和key範圍來產生compaction對象
產生compaction的關鍵點是c->inputs_[0],而這裏,有了level和key範圍,可以通過current_->GetOverlappingInputs得到c->inputs_[0],但這個方法裏面做了一個限制,就是如果level不爲0的話,當level層compaction的文件大小,等於或剛超過那層文件總大小的限制(MaxBytesForLevel(int level))時,就停止將文件加到c->inputs_[0],這樣是爲了避免一次compaction的數據太多,那爲什麼要排除level 0呢?因爲,level 0的key是有交疊的,如果丟棄掉有交疊的key,那麼會影響查詢的速度,反正level 0的文件總數沒多大,所以在決策上會傾向於查詢。
bool Compaction::IsTrivialMove()
作用:看是不是可以直接移動到下一層;如果做compaction的level的文件數等於1,而下一層,沒有文件,並且和GrandParent交疊的字節數沒有超過限制,就返回true;
void Compaction::AddInputDeletions(VersionEdit* edit)
作用:將要刪除的文件加入edit->deleted_files_對象裏面
bool Compaction::IsBaseLevelForKey(const Slice& user_key)
作用 如果從做compaction的level層的level + 2層開始找,所有的文件都不包含要查找的key,那麼就返回true,暫時還不知道幹嘛用的,留作之後再分析。
bool Compaction::ShouldStopBefore(const Slice& internal_key)
作用:暫時還不知道幹嘛用的,留作之後再分析。