11 VersionSet分析之2
11.4 LogAndApply()
前面接口小節中講過其功能:在currentversion上應用指定的VersionEdit,生成新的MANIFEST信息,保存到磁盤上,並用作current version,故爲Log And Apply。
參數edit也會被函數修改。
11.4.1 函數流程
S1 爲edit設置log number等4個計數器。
if (edit->has_log_number_) {
assert(edit->log_number_>= log_number_);
assert(edit->log_number_< next_file_number_);
} else edit->SetLogNumber(log_number_);
if(!edit->has_prev_log_number_) edit->SetPrevLogNumber(prev_log_number_);
edit->SetNextFile(next_file_number_);
edit->SetLastSequence(last_sequence_);
要保證edit自己的log number是比較大的那個,否則就是致命錯誤。保證edit的log number小於next file number,否則就是致命錯誤-見9.1小節。
S2 創建一個新的Version v,並把新的edit變動保存到v中。
Version* v = new Version(this);
{
Builder builder(this,current_);
builder.Apply(edit);
builder.SaveTo(v);
}
Finalize(v); //如前分析,只是爲v計算執行compaction的最佳level
S3 如果MANIFEST文件指針不存在,就創建並初始化一個新的MANIFEST文件。這隻會發生在第一次打開數據庫時。這個MANIFEST文件保存了current version的快照。
std::string new_manifest_file;
Status s;
if (descriptor_log_ == NULL) {
// 這裏不需要unlock *mu因爲我們只會在第一次調用LogAndApply時
// 才走到這裏(打開數據庫時).
assert(descriptor_file_ ==NULL); // 文件指針和log::Writer都應該是NULL
new_manifest_file =DescriptorFileName(dbname_, manifest_file_number_);
edit->SetNextFile(next_file_number_);
s =env_->NewWritableFile(new_manifest_file, &descriptor_file_);
if (s.ok()) {
descriptor_log_ = new log::Writer(descriptor_file_);
s =WriteSnapshot(descriptor_log_); // 寫入快照
}
}
S4 向MANIFEST寫入一條新的log,記錄current version的信息。在文件寫操作時unlock鎖,寫入完成後,再重新lock,以防止浪費在長時間的IO操作上。
mu->Unlock();
if (s.ok()) {
std::string record;
edit->EncodeTo(&record);// 序列化current version信息
s =descriptor_log_->AddRecord(record); // append到MANIFEST log中
if (s.ok()) s =descriptor_file_->Sync();
if (!s.ok()) {
Log(options_->info_log,"MANIFEST write: %s\n", s.ToString().c_str());
if (ManifestContains(record)) { // 返回出錯,其實確實寫成功了
Log(options_->info_log, "MANIFEST contains log record despiteerror ");
s = Status::OK();
}
}
}
//如果剛纔創建了一個MANIFEST文件,通過寫一個指向它的CURRENT文件
//安裝它;不需要再次檢查MANIFEST是否出錯,因爲如果出錯後面會刪除它
if (s.ok() &&!new_manifest_file.empty()) {
s = SetCurrentFile(env_,dbname_, manifest_file_number_);
}
mu->Lock();
S5 安裝這個新的version
if (s.ok()) { // 安裝這個version
AppendVersion(v);
log_number_ =edit->log_number_;
prev_log_number_ =edit->prev_log_number_;
} else { // 失敗了,刪除
delete v;
if(!new_manifest_file.empty()) {
delete descriptor_log_;
delete descriptor_file_;
descriptor_log_ =descriptor_file_ = NULL;
env_->DeleteFile(new_manifest_file);
}
}
流程的S4中,函數會檢查MANIFEST文件是否已經有了這條record,那麼什麼時候會有呢?主函數使用到了幾個新的輔助函數WriteSnapshot,ManifestContains和SetCurrentFile,下面就來分析。
11.4.2 WriteSnapshot()
把currentversion保存到*log中,信息包括comparator名字、compaction點和各級sstable文件,函數邏輯很直觀。
S1 首先聲明一個新的VersionEdit edit;
S2 設置comparator:edit.SetComparatorName(icmp_.user_comparator()->Name());
S3 遍歷所有level,根據compact_pointer_[level],設置compaction點:
edit.SetCompactPointer(level, key);
S4 遍歷所有level,根據current_->files_,設置sstable文件集合:edit.AddFile(level, xxx)
S5 根據序列化並append到log(MANIFEST文件)中;
std::string record;
edit.EncodeTo(&record);
returnlog->AddRecord(record);
以上就是WriteSnapshot的代碼邏輯。
11.4.3 ManifestContains()
如果當前MANIFEST包含指定的record就返回true,來看看函數邏輯。
S1 根據當前的manifest_file_number_文件編號打開文件,創建SequentialFile對象
S2 根據創建的SequentialFile對象創建log::Reader,以讀取文件
S3 調用log::Reader的ReadRecord依次讀取record,如果和指定的record相同,就返回true,沒有相同的record就返回false
SetCurrentFile很簡單,就是根據指定manifest文件編號,構造出MANIFEST文件名,並寫入到CURRENT即可。
11.5 ApproximateOffsetOf()
在指定的version中查找指定key的大概位置。
假設version中有n個sstable文件,並且落在了地i個sstable的key空間內,那麼返回的位置= sstable1文件大小+sstable2文件大小 + … + sstable (i-1)文件大小
+ key在sstable i中的大概偏移。
可分爲兩段邏輯。
1 首先直接和sstable的max key作比較,如果key > max key,直接跳過該文件,還記得sstable文件是有序排列的。
對於level >0的文件集合而言,如果如果key < sstable文件的min key,則直接跳出循環,因爲後續的sstable的min key肯定大於key。
2 key在sstable i中的大概偏移使用的是Table:: ApproximateOffsetOf(target)接口,前面分析過,它返回的是Table中>= target的key的位置。
VersionSet的相關函數暫時分析到這裏,compaction部分後需單獨分析。