leveldb源碼學習——db讀寫流程

讀流程

當用戶調用Get方法從leveldb中讀取一個key的value時,leveldb按照memtable,imm,sst文件的順序,依次尋找key。其中,在sst文件中搜索是通過在當前version中Get來實現的,version是sst文件的一個snapshot,可以保證版本一致,如果沒有version機制,在get過程中,此key所在sst文件被合併到了其他文件中,則可能會get失敗。

關鍵代碼

Status DBImpl::Get(const ReadOptions& options,
                   const Slice& key,
                   std::string* value) {
  Status s;
  MutexLock l(&mutex_);
  SequenceNumber snapshot;
  if (options.snapshot != NULL) {
    snapshot = reinterpret_cast<const SnapshotImpl*>(options.snapshot)->number_;
  } else {
    snapshot = versions_->LastSequence();
  }

  MemTable* mem = mem_;
  MemTable* imm = imm_;
  Version* current = versions_->current();
  mem->Ref();
  if (imm != NULL) imm->Ref();
  current->Ref();

  bool have_stat_update = false;
  Version::GetStats stats;

  // Unlock while reading from files and memtables
  {
    mutex_.Unlock();
    // First look in the memtable, then in the immutable memtable (if any).
    LookupKey lkey(key, snapshot);
    //依次從mem,imm,current version中get對應的value
    if (mem->Get(lkey, value, &s)) {
      // Done
    } else if (imm != NULL && imm->Get(lkey, value, &s)) {
      // Done
    } else {
      s = current->Get(options, lkey, value, &stats);
      have_stat_update = true;
    }
    mutex_.Lock();
  }

  if (have_stat_update && current->UpdateStats(stats)) {
    MaybeScheduleCompaction();
  }
  mem->Unref();
  if (imm != NULL) imm->Unref();
  current->Unref();
  return s;
}

需要注意的點

  • 用戶通過user-key來查詢,在leveldb中首先會通過user-key和sequence num組成lookupkey,在三處查詢,均使用lookupkey

  • Get時會首先獲取鎖,在臨界區獲取mem,imm,current version,當前seq等信息,而從三處查詢時,不需要持有鎖,因爲對以上三處是隻讀

  • Get也可能會觸發compaction,每個get會導致對sst文件seek,leveldb會記錄對每個sst文件的seek次數,到達某個值之後,會將此文件compaction,其中的道理是,假設用戶查詢key是隨機的,那文件越大,其被seek的次數應該越多,leveldb假設16k對應一次查詢,比如文件大小爲160k,那麼它最多被seek10次,seek次數超過了10,就說明它的range太大了,應該被compaction(存疑?)

  • 對sst文件的搜索,其實是按照level0,level1…的順序進行的,但是這些細節都被封裝在version->Get函數中,查看version->Get函數可以看到,首先根據key找到對應的file meta信息(version中保存了各個level的file meta信息),然後根據file meta信息(file number)到cache中找獲取對應的TableAndFile(如果沒有,則RandomAccess方式打開對應的文件),然後通過Table中的index block,filter block等信息來具體定位key在文件中的位置,期間,可能使用block cache。

  • MaybeSchedualCompaction函數會將一次compaction的判斷和compaction放到後臺任務隊列中,後臺會有一個bg線程消費此隊列

寫流程

大致上, 寫入一條k-v的流程爲

  • 將k-v包裝成爲一個writer結構
  • 放到一個writers 隊列中,等待,直到被喚醒(自己的任務被執行或者自己的任務在隊列頭)
  • 如果線程發現自己的任務在隊列頭,則將隊列中目前所有的writer組合起來,先寫log,再寫入mem
  • 如果線程發現自己的任務已經被執行,則返回

類比如下場景
一羣人要去一個窗口交表, 先排隊,如果發現自己不在隊首,就睡覺,如果自己在隊首,就把當前排隊的人羣中所有的表都收上來,代爲遞交,然後再挨個通知排隊的人他的表已經交上去了,可以回家了。

需要注意的是

  • 系統中任意時刻, 只有一個線程在執行寫mem的操作,所以寫入mem不需要加鎖
  • 每個線程都可能成爲執行寫入mem的線程

還沒有想通的問題
爲什麼這樣會提高效率?

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