Leveldb源碼分析--19

11 VersionSet分析之2

11.4 LogAndApply()

函數聲明:Status LogAndApply(VersionEdit*edit, port::Mutex* mu)
前面接口小節中講過其功能:在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()

函數聲明:Status WriteSnapshot(log::Writer*log)
把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()

函數聲明:bool ManifestContains(conststd::string& record)
如果當前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()

函數聲明:uint64_tApproximateOffsetOf(Version* v, const InternalKey& ikey)
在指定的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部分後需單獨分析。

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