leveldb源碼分析--Iterator遍歷數據庫


在DBImpl中有一個函數聲明為Iterator* DBImpl::NewIterator(const ReadOptions& options) ,他返回一個可以遍歷或者搜索數據庫的迭代器句柄。

Iterator* DBImpl::NewIterator(const ReadOptions& options) {
  SequenceNumber latest_snapshot;
  uint32_t seed;
  Iterator* iter = NewInternalIterator(options, &latest_snapshot, &seed);
  return NewDBIterator(
      this, user_comparator(), iter,
      (options.snapshot != NULL
       ? reinterpret_cast<const SnapshotImpl*>(options.snapshot)->number_
       : latest_snapshot),

可以看到這個函數就是獲得一個內部迭代器句柄然后再用NewDBIterator包裝返回一個DBIter,這個DBIter的目的就是作為內部迭代器的橋接封裝的作用,方便用戶調用。其接口函數大致有:

virtual bool Valid() const;
  virtual Slice key() cons;
  virtual Slice value() const;
  virtual Status status() const ;
  virtual void Next();
  virtual void Prev();
  virtual void Seek(const Slice& target);
  virtual void SeekToFirst();
  virtual void SeekToLast();

這些封裝只是對InternalIterator的一個簡單封裝,他們都以依賴於一個這個InternalIterator。我們來看看InternalIterator的獲取

Iterator* DBImpl::NewInternalIterator(const ReadOptions& options,
                                      SequenceNumber* latest_snapshot,
                                      uint32_t* seed) {
  IterState* cleanup = new IterState;
  mutex_.Lock();
  *latest_snapshot = versions_->LastSequence();

  // Collect together all needed child iterators
  std::vector<Iterator*> list;
  list.push_back(mem_->NewIterator());
  mem_->Ref();
  if (imm_ != NULL) {
    list.push_back(imm_->NewIterator());
    imm_->Ref();
  }
  versions_->current()->AddIterators(options, &list);
  Iterator* internal_iter =
      NewMergingIterator(&internal_comparator_, &list[0], list.size());
  versions_->current()->Ref();

  cleanup->mu = &mutex_;
  cleanup->mem = mem_;
  cleanup->imm = imm_;
  cleanup->version = versions_->current();
  internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, NULL);

  *seed = ++seed_;
  mutex_.Unlock();
  return internal_iter;
}

這里internal_iter的獲取是從memTable、imm、還有version取得的所有與迭代器全部傳入到一個MergingIterator中。在詳細介紹這個MergingIterator前我們先來看看一個簡化的例子,並且假設此時沒有imm_table。這樣我們現在有一個Memtable,SSTable中level0有兩個文件,有一個level1的文件,里面的key大致如下(我們根據新舊程度排序,數據新舊依據請查閱Compaction章節):

Memtable:  1,2,3,4,5,6

level0-2:     3,4,7,6

level0-1:     2,3,4,6

level1:        1,3,6,7,9…

這樣,如果我們開始從第一個key開始以遞增(Next)的方式遍歷整個數據庫,那么我們可以見到如下的過程。首先是每個初始化一個指向當前文件(這里暫時將Memtable也當做一個文件)第一個位置的指針,如下紅色表示當前指向的指針:

Memtable:  1,3,4,5,6

level0-2:     3,4,7,6

level0-1:     2,3,4,6

level1:        1,3,6,7,9…

這樣,我們根據數據最新關系我們很容易判斷第一個key應該為Memtable中的1,我們記該當前key為1。然后再調用Next,調用Next的時候就需要將Memtable和level1中的當前指針key為1的向后一個key(level1中的移動在leveldb中是在FindSmallest中進行的),得到如下:

Memtable:  1,3,4,5,6

level0-2:     3,4,7,6

level0-1:     2,3,4,6

level1:        1,2,6,7,9…

那么這個時候我們也很容以判斷這里的next的值應該是level0-1中的2那么我做出這個判斷的過程是怎么樣的呢?應該是找出當前每個文件中指針指向的值中最小的那個key,如果有多個文件中當前指針key相同的時候,那么就應該取最新的那個文件中。再繼續Next,

Memtable:  1,3,4,5,6

level0-2:     3,4,7,6

level0-1:     2,3,4,6

level1:        1,2,6,7,9…

那么此時應該是Memtable中的3。那么此時我們需要先前查找當前3的前一個呢?很明顯我們應該回到上面第三個圖的狀態,應該level1,level0-1都進行回溯,然后選擇最小的那個。但是如何能回到該狀態呢?如果這樣的話我們必須記錄每次移動的過程,這種過程性的記錄在程序設計中是十分難以做到的。而在leveldb中也采用了另外一種方式,就是在我們的迭代器器中記錄一個當前遍歷的值比如此時的level0-1中的3進行一個Prev,然后再查找最大值,最大值方式的時候如果大於3就繼續往前回溯,再找到最大的最新的。形成的狀態如下:

Memtable:  1,3,4,5,6                     //找到3,然后在prev到1

level0-2:     3,4,7,6                       //此處其實應該為invalid,即找到3,prev到invalid

level0-1:     2,3,4,6                      //找到3,prev到2

level1:        1,2,6,7,9…                 /找到6,prev到2

而查找的最大最新值也應該是level0-1中的2。

下面我們來看看代碼Prev的實現:

virtual void Prev() {
  if (direction_ != kReverse) {// 如果之前遍歷方向向后
      for (int i = 0; i < n_; i++) {
        IteratorWrapper* child = &children_[i];
        if (child != current_) {
          child->Seek(key());// 查找遍歷當前值,然后再往前回溯
          if (child->Valid()) {
            // Child is at first entry >= key().  Step back one to be < key()
            child->Prev();
          } else {
            //沒有>當前key值的key.
            child->SeekToLast();
          }
        }
      }
      direction_ = kReverse;
    }

    current_->Prev();
    FindLargest();
  }
virtual void Next() {
    if (direction_ != kForward) {
      for (int i = 0; i < n_; i++) {
        IteratorWrapper* child = &children_[i];
        if (child != current_) {
          child->Seek(key());
          if (child->Valid() &&// 如果key為當前key相等,向后next
              comparator_->Compare(key(), child->key()) == 0) {
            child->Next();
          }
        }
      }
      direction_ = kForward;
    }

    current_->Next();
    FindSmallest();
  }

所以再調用Next的過程就為:查找 >= 2的,如果找到並且==2就Next,然后找最小的最新的一個位置。

Memtable:  1,3,4,5,6                //找到3

level0-2:    3,4,7,6                  //找到3

level0-1:     2,3,4,6                  //這里是先找到2,然后再Next

level1:        1,2,6,7,9…              //同上

所以這里的操作就變成了查找

 

我們詳細看看Next

void DBIter::Next() {
  assert(valid_);

  if (direction_ == kReverse) {  // Switch directions?
    direction_ = kForward;
    // 如果上次已經到最后,回溯到第一個
    if (!iter_->Valid()) {
      iter_->SeekToFirst();
    } else {
      iter_->Next();
    }
    if (!iter_->Valid()) {
      valid_ = false;
      saved_key_.clear();
      return;
    }
    // saved_key_ already contains the key to skip past.
  } else {
    // 存儲當前key,以備下次為Prev時查找這個key.
    SaveKey(ExtractUserKey(iter_->key()), &saved_key_);
  }

  FindNextUserEntry(true, &saved_key_);
}

這里由於leveldb遍歷數據庫時涉及到多個數據文件及內存中的Memtable,所以每次調用prev和next時會有比較復雜的處理。

void DBIter::FindNextUserEntry(bool skipping, std::string* skip) {
  // Loop until we hit an acceptable entry to yield
  assert(iter_->Valid());
  assert(direction_ == kForward);
  do {
    ParsedInternalKey ikey;
    if (ParseKey(&ikey) && ikey.sequence <= sequence_) {
      switch (ikey.type) {
        case kTypeDeletion:
          // 如果為刪除,標記后面的已刪除的key應該跳過
          // 保存跳過的key
          SaveKey(ikey.user_key, skip);
          skipping = true;
          break;
        case kTypeValue:
          if (skipping &&
              user_comparator_->Compare(ikey.user_key, *skip) <= 0) {
            // 小於等於,跳過
          } else {//找到值,返回
            valid_ = true;
            saved_key_.clear();
            return;
          }
          break;
      }
    }
    iter_->Next();
  } while (iter_->Valid());
  saved_key_.clear();
  valid_ = false;
}

這里我們不再對DBIter中的其他函數進行一一介紹,比如Prev和這里也是一個類似的(但是比較相反)處理過程。稍微提一下的是我們在void DBIter::FindPrevUserEntry() 中有如下一段代碼

if (saved_value_.capacity() > raw_value.size() + 1048576) {
            std::string empty;
            swap(empty, saved_value_);
          }
這里當saved_value中的長度超過一定的值以后我們將其交換給一個零時變量,這樣在超出其域以后析構時就可以對其內部的內存進行釋放,而如果只是改變其大小內存則得不到釋放。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM