Leveldb源碼分析--22

14 DB的查詢與遍歷之2

14.4 DBIter

Leveldb數據庫的MemTable和sstable文件的存儲格式都是(user key, seq, type) => uservalue。DBIter把同一個userkey在DB中的多條記錄合併爲一條,綜合考慮了userkey的序號、刪除標記、和寫覆蓋等等因素。
從前面函數NewIterator的代碼還能看到,DBIter內部使用了MergingIterator,在調用MergingItertor的系列seek函數後,DBIter還要處理key的刪除標記。否則,遍歷時會把已刪除的key列舉出來。
DBIter還定義了兩個移動方向,默認是kForward:
1) kForward,向前移動,代碼保證此時DBIter的內部迭代器剛好定位在this->key(),this->value()這條記錄上;
2) kReverse,向後移動,代碼保證此時DBIter的內部迭代器剛好定位在所有key=this->key()的entry之前。
其成員變量savedkey和saved value保存的是KReverse方向移動時的k/v對,每次seek系調用之後,其值都會跟隨iter_而改變。
DBIter的代碼開始讀來感覺有些繞,主要就是它要處理刪除標記,而且其底層的MergingIterator,對於同一個key會有多個不同sequence的entry。導致其Next/Prev操作比較複雜,要考慮到上一次移動的影響,跳過刪除標記和重複的key。
DBIter必須導出Iterator定義的幾個接口,下面就拖出來挨個分析。

14.4.1 Get系接口

首先是幾個簡單接口,獲取key、value和status的:

  virtual Slice key() const { //kForward直接取iter_->key(),否則取saved key
    assert(valid_);
    return (direction_ ==kForward) ? ExtractUserKey(iter_->key()) : saved_key_;
  }

  virtual Slice value() const { //kForward直接取iter_->value(),否則取saved value
    assert(valid_);
    return (direction_ ==kForward) ? iter_->value() : saved_value_;
  }

  virtual Status status() const {
    if (status_.ok()) returniter_->status();
    return status_;
  }

14.4.2 輔助函數

在分析seek系函數之前,先來理解兩個重要的輔助函數:FindNextUserEntry和FindPrevUserEntry的功能和邏輯。其功能就是循環跳過下一個/前一個delete的記錄,直到遇到kValueType的記錄。
先來看看,函數聲明爲:
void DBIter::FindNextUserEntry(bool skipping, std::string* skip)
參數@skipping表明是否要跳過sequence更小的entry;
參數@skip臨時存儲空間,保存seek時要跳過的key;
在進入FindNextUserEntry時,iter_剛好定位在this->key(), this->value()這條記錄上。下面來看函數實現:
  // 循環直到找到合適的entry,direction必須是kForward
  assert(iter_->Valid());
  assert(direction_ == kForward);
  do {
    ParsedInternalKey ikey;
    // 確保iter_->key()的sequence <= 遍歷指定的sequence
    if (ParseKey(&ikey)&& ikey.sequence <= sequence_) {
      switch (ikey.type) {
        case kTypeDeletion:
          //對於該key,跳過後面遇到的所有entry,它們被這次刪除覆蓋了
          //保存key到skip中,並設置skipping=true
          SaveKey(ikey.user_key,skip);
          skipping = true;
          break;
        case kTypeValue:
          if (skipping &&
             user_comparator_->Compare(ikey.user_key, *skip) <= 0) {
            // 這是一個被刪除覆蓋的entry,或者user key比指定的key小,跳過
          } else { // 找到,清空saved key並返回,iter_已定位到正確的entry
            valid_ = true;
            saved_key_.clear();
            return;
          }
          break;
      }
    }
    iter_->Next(); // 繼續檢查下一個entry
  } while (iter_->Valid());
  // 到這裏表明已經找到最後了,沒有符合的entry
  saved_key_.clear();
  valid_ = false;
FindNextUserKey移動方向是kForward,DBIter在向kForward移動時,借用了saved key作爲臨時緩存。FindNextUserKey確保定位到的entry的sequence不會大於指定的sequence,並跳過被刪除標記覆蓋的舊記錄。
接下來是FindPrevUserKey,函數聲明爲:void DBIter::FindPrevUserEntry(),在進入FindPrevUserEntry時,iter_剛好位於saved key對應的所有記錄之前。源代碼如下:
  assert(direction_ == kReverse); // 確保是kReverse方向
  ValueType value_type =kTypeDeletion; //後面的循環至少執行一次Prev操作
  if (iter_->Valid()) {
    do { // 循環
      // 確保iter_->key()的sequence <= 遍歷指定的sequence
      ParsedInternalKey ikey;
      if (ParseKey(&ikey)&& ikey.sequence <= sequence_) {
        if ((value_type !=kTypeDeletion) &&
           user_comparator_->Compare(ikey.user_key, saved_key_) < 0) {
           break; // 我們遇到了前一個key的一個未被刪除的entry,跳出循環
           // 此時Key()將返回saved_key,saved key非空;
        }
        //根據類型,如果是Deletion則清空saved key和saved value
        //否則,把iter_的user key和value賦給saved key和saved value
        value_type = ikey.type;
        if (value_type ==kTypeDeletion) {
          saved_key_.clear();
          ClearSavedValue();
        } else {
          Slice raw_value =iter_->value();
          if(saved_value_.capacity() > raw_value.size() + 1048576) {
            std::string empty;
            swap(empty,saved_value_);
          }
         SaveKey(ExtractUserKey(iter_->key()), &saved_key_);
         saved_value_.assign(raw_value.data(), raw_value.size());
        }
      }
      iter_->Prev(); // 前一個
    } while (iter_->Valid());
  }
  if (value_type == kTypeDeletion){ // 表明遍歷結束了,將direction設置爲kForward
    valid_ = false;
    saved_key_.clear();
    ClearSavedValue();
    direction_ = kForward;
  } else {
    valid_ = true;
  }
函數FindPrevUserKey根據指定的sequence,依次檢查前一個entry,直到遇到user key小於saved key,並且類型不是Delete的entry。
如果entry的類型是Delete,就清空saved key和saved value,這樣在依次遍歷前一個entry的循環中,只要類型不是Delete,就是要找的entry。這就是Prev的語義。

14.4.3 Seek系函數

瞭解了這兩個重要的輔助函數,可以分析幾個Seek接口了,它們需要藉助於上面的這兩個函數來跳過被delete的記錄。

void DBIter::Seek(const Slice& target) {
  direction_ = kForward; // 向前seek
  // 清空saved value和saved key,並根據target設置saved key
  ClearSavedValue();
  saved_key_.clear();
  AppendInternalKey( // kValueTypeForSeek(1) > kDeleteType(0)
      &saved_key_,ParsedInternalKey(target, sequence_, kValueTypeForSeek));
  iter_->Seek(saved_key_); // iter seek到saved key
  //可以定位到合法的iter,還需要跳過Delete的entry
  if (iter_->Valid()) FindNextUserEntry(false,&saved_key_);
  else valid_ = false;
}                                                                                                                                       

void DBIter::SeekToFirst() {
  direction_ = kForward; // 向前seek
  // 清空saved value,首先iter_->SeekToFirst,然後跳過Delete的entry
  ClearSavedValue();
  iter_->SeekToFirst();
  if (iter_->Valid()) FindNextUserEntry(false,&saved_key_ /*臨時存儲*/);
  else valid_ = false;
}

void DBIter::SeekToLast() { // 更簡單
  direction_ = kReverse;
  ClearSavedValue();
  iter_->SeekToLast();
  FindPrevUserEntry();
}

14.4.4 Prev()和Next()

Next和Prev接口,相對複雜一些。和底層的merging iterator不同,DBIter的Prev和Next步進是以key爲單位的,而mergingiterator是以一個record爲單位的。所以在調用merging Iterator做Prev和Next迭代時,必須循環直到key發生改變。
這次讓我們以Prev爲例,以14.4-1圖解一下,還真是一圖勝千言啊。
假設指定讀取的sequence爲2,當前iter在key4:2:1上,direction爲kForward。此時調用Prev(),此圖顯示了Prev操作執行的5個步驟。

圖14.4-1

S1 首先因爲direction爲kForward,先調整iter到key3:1:1上。此圖也說明了調整的理由,key4:2:1前面還有key4:3:1。然後進入FindPrevUserEntry函數,執行S2到S4。
S2 跳到key3:2:0上時,這是一個刪除標記,清空saved key(其中保存的是key3:1:1)。
S3 循環繼續,跳到key2:1:1上,此時key2:1:1 > saved key,設置saved key爲key2:1:1,並繼續循環。
S4 循環繼續,跳到key2:2:1上,此時key2:2:1 > saved key,設置saved key爲key2:2:1,並繼續循環。
S5 跳到Key1:1:1上,因爲key1:1:1 < saved key,跳出循環。
最終狀態iter_位置在key1:1:1上,而saved key保存的則是key2:2:1上,這也就是Prev應該定位到的值。也就是說在Prev操作下,iter_的位置並不是真正的key位置。這就是前面Get系函數中,在direction爲kReverse時,返回saved key/value的原因。
同理,在Next時,如果direction是kReverse,根據上面的Prev可以發現,此時iter剛好是saved key的前一個entry。執行iter->Next()就跳到了saved key的dentry範圍的sequence最大的那個entry。在前面的例子中,在Prev後執行Next,那麼iter首先跳轉到key2:3:1上,然後再調用FindNextUserEntry循環,使iter定位在key2:2:1上。
下面首先來分析Next的實現。如果direction是kReverse,表明上一次做的是kReverse跳轉,這種情況下,iter_位於key是this->key()的所有entry之前,我們需要先把iter_跳轉到this->key()對應的entries範圍內。
void DBIter::Next() {
  assert(valid_);
  if (direction_ == kReverse) { //需要預處理,並更改direction=kForward
    direction_ = kForward;
    // iter_剛好在this->key()的所有entry之前,所以先跳轉到this->key()
    // 的entries範圍之內,然後再做常規的skip
    if (!iter_->Valid()) iter_->SeekToFirst();
    else iter_->Next();
    if (!iter_->Valid()) {
      valid_ = false;
      saved_key_.clear();
      return;
    }
  }
  // 把saved_key_ 用作skip的臨時存儲空間
  std::string* skip =&saved_key_;
  SaveKey(ExtractUserKey(iter_->key()), skip);// 設置skip爲iter_->key()的user key
  FindNextUserEntry(true, skip);
}
接下來是Prev(),其實和Next()邏輯相似,但方向相反。
如果direction是kForward,表明上一次是做的是kForward跳轉,這種情況下,iter_指向當前的entry,我們需要調整iter,使其指向到前一個key,iter的位置是這個key所有record序列的最後一個,也就是sequence最小的那個record。
void DBIter::Prev() {
  assert(valid_);
  if (direction_ == kForward) { //需要預處理,並更改direction
    // iter_指向當前的entry,向後掃描直到key發生改變,然後我們可以做
    //常規的reverse掃描
    assert(iter_->Valid());  // iter_必須合法,並把saved key設置爲iter_->key()
    SaveKey(ExtractUserKey(iter_->key()), &saved_key_);
    while (true) {
      iter_->Prev();
      if (!iter_->Valid()) { // 到頭了,直接返回
        valid_ = false;
        saved_key_.clear();
        ClearSavedValue();
        return;
      }
      if (user_comparator_->Compare(ExtractUserKey(iter_->key()),
                                   saved_key_) < 0) {
        break; // key變化就跳出循環,此時iter_剛好位於saved key對應的所有entry之前
      }
    }
    direction_ = kReverse;
  }
  FindPrevUserEntry();
}

接下來要分析的是插入和刪除操作。

14.5 小結

查詢操作並不複雜,只需要根據seq找到最新的記錄即可。知道leveldb的遍歷會比較複雜,不過也沒想到會這麼複雜。這主要是得益於sstable 0的重合性,以及memtable和sstable文件的重合性。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章