Leveldb源碼分析–16

來源:互聯網
上載者:User
文章目錄
  • 10.1 Version介面
  • 10.2 Version::AddIterators()
  • 10.3 Version::LevelFileNumIterator類
  • 10.4 Version::Get()
10 Version分析之1

先來分析leveldb對單版本的sstable檔案管理,主要集中在Version類中。前面的10.4節已經說明了Version類的功能和成員,這裡分析其函數介面和代碼實現。
Version不會修改其管理的sstable檔案,只有讀取操作。10.1 Version介面

先來看看Version類的介面函數,接下來再一一分析。 

  // 追加一系列iterator到 @*iters中,將在merge到一起時產生該Version的內容  // 要求: Version已經儲存了(見VersionSet::SaveTo)  void AddIterators(constReadOptions&, std::vector<Iterator*>* iters);  // 給定@key尋找value,如果找到儲存在@*val並返回OK。  // 否則返回non-OK,設定@ *stats.  // 要求:沒有hold lock  struct GetStats {    FileMetaData* seek_file;    int seek_file_level;  };  Status Get(constReadOptions&, const LookupKey& key, std::string* val,GetStats* stats);  // 把@stats加入到目前狀態中,如果需要觸發新的compaction返回true  // 要求:hold lock  bool UpdateStats(constGetStats& stats);  void GetOverlappingInputs(intlevel,      const InternalKey*begin,         // NULL 指在所有key之前      const InternalKey* end,           // NULL指在所有key之後      std::vector<FileMetaData*>* inputs);  // 如果指定level中的某些檔案和[*smallest_user_key,*largest_user_key]有重合就返回true。  // @smallest_user_key==NULL表示比DB中所有key都小的key.  // @largest_user_key==NULL表示比DB中所有key都大的key.  bool OverlapInLevel(int level,const Slice*smallest_user_key, const Slice* largest_user_key);  // 返回我們應該在哪個level上放置新的memtable compaction,  // 該compaction覆蓋了範圍[smallest_user_key,largest_user_key].  int PickLevelForMemTableOutput(const Slice& smallest_user_key,                             const Slice& largest_user_key);  int NumFiles(int level) const {return files_[level].size(); } // 指定level的sstable個數

10.2 Version::AddIterators()

該函數最終在DB::NewIterators()介面中被調用,調用層次為:
DBImpl::NewIterator()->DBImpl::NewInternalIterator()->Version::AddIterators()。
函數功能是為該Version中的所有sstable都建立一個Two Level Iterator,以遍曆sstable的內容。
對於level=0層級的sstable檔案,直接通過TableCache::NewIterator()介面建立,這會直接載入sstable檔案到記憶體cache中。
對於level>0層級的sstable檔案,通過函數NewTwoLevelIterator()建立一個TwoLevelIterator,這就使用了lazy open的機制。
下面來分析函數代碼:
S1 對於level=0層級的sstable檔案,直接裝入cache,level0的sstable檔案可能有重合,需要merge。

  for (size_t i = 0; i <files_[0].size(); i++) {   iters->push_back(vset_->table_cache_->NewIterator(// versionset::table_cache_            options,files_[0][i]->number, files_[0][i]->file_size));  }

S2 對於level>0層級的sstable檔案,lazy open機制,它們不會有重疊。

  for (int ll = 1; ll <config::kNumLevels; ll++) {    if(!files_[ll].empty()) iters->push_back(NewConcatenatingIterator(options,level));  }

函數NewConcatenatingIterator()直接返回一個TwoLevelIterator對象:
return NewTwoLevelIterator(new LevelFileNumIterator(vset_->icmp_,&files_[level]),
                                                                                  &GetFileIterator,vset_->table_cache_, options);
其第一級iterator是一個LevelFileNumIterator,第二級的迭代函數是GetFileIterator,下面就來分別分析之。
GetFileIterator是一個靜態函數,很簡單,直接返回TableCache::NewIterator()。函式宣告為:
static Iterator* GetFileIterator(void* arg,const ReadOptions& options, constSlice& file_value)

  TableCache* cache =reinterpret_cast<TableCache*>(arg);  if (file_value.size() != 16) { // 錯誤    returnNewErrorIterator(Status::Corruption("xxx"));  } else {    returncache->NewIterator(options,                  DecodeFixed64(file_value.data()), // filenumber                  DecodeFixed64(file_value.data() + 8)); // filesize  }

這裡的file_value是取自於LevelFileNumIterator的value,它的value()函數把file number和size以Fixed 8byte的方式壓縮成一個Slice對象並返回。10.3 Version::LevelFileNumIterator類

這也是一個繼承者Iterator的子類,一個內部Iterator。給定一個version/level對,產生該level內的檔案資訊。對於給定的entry,key()返回的是檔案中所包含的最大的key,value()返回的是|file number(8 bytes)|file size(8 bytes)|串。
它的建構函式接受兩個參數:InternalKeyComparator&,用於key的比較;vector<FileMetaData*>*,指向version的所有sstable檔案清單。
LevelFileNumIterator(const InternalKeyComparator& icmp,
                                      const std::vector<FileMetaData*>* flist)
                                      : icmp_(icmp), flist_(flist),index_(flist->size()) {} // Marks as invalid
來看看其介面實現,不限羅嗦,全部都列出來。

Valid函數、SeekToxx和Next/Prev函數都很簡單,畢竟容器是一個vector。Seek函數調用了FindFile,這個函數後面會分析。

  virtual void Seek(constSlice& target) { index_ = FindFile(icmp_, *flist_, target);}  virtual void SeekToFirst() {index_ = 0; }  virtual void SeekToLast() {index_ = flist_->empty() ? 0 : flist_->size() - 1;}  virtual void Next() {    assert(Valid());    index_++;  }  virtual void Prev() {    assert(Valid());    if (index_ == 0) index_ =flist_->size(); // Marks as invalid    else index_--;  }  Slice key() const {    assert(Valid());    return(*flist_)[index_]->largest.Encode(); // 返回當前sstable包含的largest key  }  Slice value() const { // 根據|number|size|的格式Fixed int壓縮    assert(Valid());    EncodeFixed64(value_buf_,(*flist_)[index_]->number);    EncodeFixed64(value_buf_+8,(*flist_)[index_]->file_size);    return Slice(value_buf_,sizeof(value_buf_));  }

來看FindFile,這其實是一個二分尋找函數,因為傳入的sstable檔案清單是有序的,因此可以使用二分尋找演算法。就不再列出代碼了。

10.4 Version::Get()

尋找函數,直接在DBImpl::Get()中被調用,函數原型為:
Status Version::Get(const ReadOptions& options, constLookupKey& k, std::string* value, GetStats* stats)
如果本次Get不止seek了一個檔案(僅會發生在level 0的情況),就將搜尋的第一個檔案儲存在stats中。如果stat有資料返回,表明本次讀取在搜尋到包含key的sstable檔案之前,還做了其它無謂的搜尋。這個結果將用在UpdateStats()中。
這個函數邏輯還是有些複雜的,來看看代碼。
S1 首先,取得必要的資訊,初始化幾個臨時變數

  Slice ikey = k.internal_key();  Slice user_key = k.user_key();  const Comparator* ucmp =vset_->icmp_.user_comparator();  Status s;  stats->seek_file = NULL;  stats->seek_file_level = -1;  FileMetaData* last_file_read =NULL; // 在找到>1個檔案時,讀取時記錄上一個  int last_file_read_level = -1;        // 這僅發生在level 0的情況下  std::vector<FileMetaData*>tmp;  FileMetaData* tmp2;

S2 從0開始遍曆所有的level,依次尋找。因為entry不會跨越level,因此如果在某個level中找到了entry,那麼就無需在後面的level中尋找了。

for (int level = 0; level <config::kNumLevels; level++) {  size_t num_files = files_[level].size();  if (num_files == 0) continue; // 本層沒有檔案,則直接跳過   // 取得level下的所有sstable檔案清單,搜尋本層  FileMetaData* const* files = &files_[level][0]; 

後面的所有邏輯都在for迴圈體中。
S3 遍曆level下的sstable檔案清單,搜尋,注意對於level=0和>0的sstable檔案的處理,由於level 0檔案之間的key可能有重疊,因此處理邏輯有別於>0的level。
    S3.1 對於level 0,檔案可能有重疊,找到所有和user_key有重疊的檔案,然後根據時間順序從最新的檔案依次處理。

   tmp.reserve(num_files);   for (uint32_t i = 0; i <num_files; i++) { // 遍曆level 0下的所有sstable檔案     FileMetaData* f =files[i];     if(ucmp->Compare(user_key, f->smallest.user_key()) >= 0 &&     ucmp->Compare(user_key, f->largest.user_key()) <= 0)     tmp.push_back(f); // sstable檔案有user_key有重疊    }   if (tmp.empty()) continue;   std::sort(tmp.begin(),tmp.end(), NewestFirst); // 排序   files = &tmp[0]; num_files= tmp.size();// 指向tmp指標和大小

    S3.2 對於level>0,leveldb保證sstable檔案之間不會有重疊,所以處理邏輯有別於level 0,直接根據ikey定位到sstable檔案即可。

      //二分尋找,找到第一個largest key >=ikey的file index      uint32_t index =FindFile(vset_->icmp_, files_[level], ikey);      if (index >= num_files) { // 未找到,檔案不存在        files = NULL;  num_files = 0;      } else {        tmp2 = files[index];        if(ucmp->Compare(user_key, tmp2->smallest.user_key()) < 0) {          // 找到的檔案其所有key都大於user_key,等於檔案不存在          files = NULL;  num_files = 0;        } else {          files = &tmp2;  num_files = 1;        }      }

S4 遍曆找到的檔案,存在files中,其個數為num_files。
      for (uint32_t i = 0; i <num_files; ++i) {
後面的邏輯都在這一層迴圈中,只要在某個檔案中找到了k/v對,就跳出for迴圈。
S4.1 如果本次讀取不止搜尋了一個檔案,記錄之,這僅會發生在level 0的情況下。

     if(last_file_read != NULL && stats->seek_file == NULL) {        // 本次讀取不止seek了一個檔案,記錄第一個        stats->seek_file =last_file_read;        stats->seek_file_level= last_file_read_level;      }      FileMetaData* f = files[i];      last_file_read = f; // 記錄本次讀取的level和file      last_file_read_level =level;

   S4.2 調用TableCache::Get()嘗試擷取{ikey, value},如果返回OK則進入S4.3,否則直接返回,傳遞的回呼函數是SaveValue()。

      Saver saver; // 初始化saver      saver.state = kNotFound;      saver.ucmp = ucmp;      saver.user_key = user_key;      saver.value = value;      s = vset_->table_cache_->Get(options,f->number, f->file_size,                                  ikey, &saver, SaveValue);      if (!s.ok()) return s;

   S4.3 根據saver的狀態判斷,如果是Not Found則向下搜尋下一個更早的sstable檔案,其它值則返回。

      switch (saver.state) {        case kNotFound: break; // 繼續搜尋下一個更早的sstable檔案        case kFound:  return s; // 找到        case kDeleted: // 已刪除          s =Status::NotFound(Slice());  // 為了效率,使用空的錯誤字串          return s;        case kCorrupt: // 資料損毀          s =Status::Corruption("corrupted key for ", user_key);          return s;      }

以上就是Version::Get()的代碼邏輯,如果level 0的sstable檔案太多的話,會影響讀取速度,這也是為什麼進行compaction的原因。
另外,還有一個傳遞給TableCache::Get()的saver函數,下面就來簡單分析下。這是一個靜態函數:static void SaveValue(void* arg,const Slice& ikey, const Slice& v)。它內部使用了結構體Saver:
struct Saver {
    SaverState state;
    const Comparator* ucmp; // user key比較子
    Slice user_key;
    std::string* value;
};
函數SaveValue的邏輯很簡單,首先解析Table傳入的InternalKey,然後根據指定的Comparator判斷user key是否是要尋找的user key。如果是並且type是kTypeValue,則設定到Saver::*value中,並返回kFound,否則返回kDeleted。代碼如下:

  Saver* s =reinterpret_cast<Saver*>(arg);  ParsedInternalKey parsed_key; // 解析ikey到ParsedInternalKey  if (!ParseInternalKey(ikey,&parsed_key)) s->state = kCorrupt; // 解析失敗  else {    if(s->ucmp->Compare(parsed_key.user_key, s->user_key) == 0) { // 比較user key      s->state =(parsed_key.type == kTypeValue) ? kFound : kDeleted;      if (s->state == kFound)s->value->assign(v.data(), v.size()); // 找到,儲存結果    }  }

下面要分析的幾個函數,或多或少都和compaction相關。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.