VersionSet-levelDB源碼解析


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)

作用:暫時還不知道幹嘛用的,留作之後再分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章