Leveldb源碼分析--21

最近工作上事情太多,更新的也比較慢了。

14 DB的查詢與遍歷之1

分析完如何打開和關閉db,本章就繼續分析如何從db中根據key查詢value,以及遍歷整個db。

14.1 Get()

函數聲明:StatusGet(const ReadOptions& options, const Slice& key, std::string* value)
從DB中查詢key 對應的value,參數@options指定讀取操作的選項,典型的如snapshot號,從指定的快照中讀取。快照本質上就是一個sequence號,後面將單獨在快照一章中分析。
下面就來分析下函數邏輯:
// S1 鎖mutex,防止併發,如果指定option則嘗試獲取snapshot;然後增加MemTable的引用值。
  MutexLock l(&mutex_);
  SequenceNumber snapshot;
  if (options.snapshot != NULL)
    snapshot =reinterpret_cast<const SnapshotImpl*>(options.snapshot)->number_;
  else snapshot =versions_->LastSequence(); // 取當前版本的最後Sequence
  MemTable *mem = mem_, *imm =imm_;
  Version* current =versions_->current();
  mem->Ref();
  if (imm != NULL) imm->Ref();
  current->Ref();
// S2 從sstable文件和MemTable中讀取時,釋放鎖mutex;之後再次鎖mutex。
  bool have_stat_update = false;
  Version::GetStats stats;
  {
    mutex_.Unlock();
    // 先從memtable中查詢,再從immutable memtable中查詢
    LookupKey lkey(key, snapshot);
    if (mem->Get(lkey, value,&s)) {
    } else if (imm != NULL&& imm->Get(lkey, value, &s)) {
    } else { // 需要從sstable文件中查詢
      s = current->Get(options,lkey, value, &stats);
      have_stat_update = true; // 記錄之,用於compaction
    }
    mutex_.Lock();
  }
// S3 如果是從sstable文件查詢出來的,檢查是否需要做compaction。最後把MemTable的引用計數減1。
  if (have_stat_update &¤t->UpdateStats(stats)) {
    MaybeScheduleCompaction();
  }
  mem->Unref();
  if (imm != NULL)imm->Unref();
  current->Unref();
查詢是比較簡單的操作,UpdateStats在前面Version一節已經分析過。

14.2 NewIterator()

函數聲明:Iterator*NewIterator(const ReadOptions& options)
通過該函數生產了一個Iterator*對象,調用這就可以基於該對象遍歷db內容了。
函數很簡單,調用兩個函數創建了一個二級Iterator。
Iterator* DBImpl::NewIterator(const ReadOptions& options) {
  SequenceNumber latest_snapshot;
  Iterator* internal_iter =NewInternalIterator(options, &latest_snapshot);
  returnNewDBIterator(&dbname_, env_, user_comparator(), internal_iter,
      (options.snapshot != NULL
       ? reinterpret_cast<constSnapshotImpl*>(options.snapshot)->number_
       : latest_snapshot));
}

其中,函數NewDBIterator直接返回了一個DBIter指針

Iterator* NewDBIterator(const std::string* dbname, Env* env,
    const Comparator*user_key_comparator, Iterator* internal_iter,
    const SequenceNumber& sequence) {
  return new DBIter(dbname, env,user_key_comparator, internal_iter, sequence);
}
函數NewInternalIterator有一些處理邏輯,就是收集所有能用到的iterator,生產一個Merging Iterator。這包括MemTable,Immutable MemTable,以及各sstable。

Iterator* DBImpl::NewInternalIterator(const ReadOptions& options,
                                SequenceNumber*latest_snapshot) {
  IterState* cleanup = newIterState;
  mutex_.Lock();
  // 根據last sequence設置lastest snapshot,並收集所有的子iterator
  *latest_snapshot =versions_->LastSequence();
  std::vector<Iterator*>list;
  list.push_back(mem_->NewIterator()); // >memtable
  mem_->Ref();
  if (imm_ != NULL) {
   list.push_back(imm_->NewIterator()); // >immutablememtable
    imm_->Ref();
  }
  versions_->current()->AddIterators(options, &list); // >current的所有sstable
  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);
  mutex_.Unlock();
  return internal_iter;
}
這個清理函數CleanupIteratorState是很簡單的,對註冊的對象做一下Unref操作即可。
static void CleanupIteratorState(void* arg1, void* arg2) {
  IterState* state =reinterpret_cast<IterState*>(arg1);
  state->mu->Lock();
  state->mem->Unref();
  if (state->imm != NULL)state->imm->Unref();
  state->version->Unref();
  state->mu->Unlock();
  delete state;
}
可見對於db的遍歷依賴於DBIter和Merging Iterator這兩個迭代器,它們都是Iterator接口的實現子類。

14.3 MergingIterator

MergingIterator是一個合併迭代器,它內部使用了一組自Iterator,保存在其成員數組children_中。如上面的函數NewInternalIterator,包括memtable,immutable memtable,以及各sstable文件;它所做的就是根據調用者指定的key和sequence,從這些Iterator中找到合適的記錄。
在分析其Iterator接口之前,先來看看兩個輔助函數FindSmallest和FindLargest。FindSmallest從0開始向後遍歷內部Iterator數組,找到key最小的Iterator,並設置到current_;FindLargest從最後一個向前遍歷內部Iterator數組,找到key最大的Iterator,並設置到current_;
MergingIterator還定義了兩個移動方向:kForward,向前移動;kReverse,向後移動。

14.3.1 Get系接口

下面就把其接口拖出來一個一個分析,首先是簡單接口,key和value都是返回current_的值,current_是當前seek到的Iterator位置。

virtual Slice key() const {
    assert(Valid());
    return current_->key();
  }
  virtual Slice value() const {
    assert(Valid());
    return current_->value();
  }
  virtual Status status() const {
    Status status;
    for (int i = 0; i < n_;i++) { // 只有所有內部Iterator都ok時,才返回ok
      status =children_[i].status();
      if (!status.ok()) break;
    }
    return status;
  }

14.3.2 Seek系接口

然後是幾個seek系的函數,也比較簡單,都是依次調用內部Iterator的seek系函數。然後做merge,對於Seek和SeekToFirst都調用FindSmallest;對於SeekToLast調用FindLargest。

 virtual void SeekToFirst() {
    for (int i = 0; i < n_;i++) children_[i].SeekToFirst();
    FindSmallest();
    direction_ = kForward;
  }
  virtual void SeekToLast() {
    for (int i = 0; i < n_;i++) children_[i].SeekToLast();
    FindLargest();
    direction_ = kReverse;
  }
  virtual void Seek(constSlice& target) {
    for (int i = 0; i < n_;i++) children_[i].Seek(target);
    FindSmallest();
    direction_ = kForward;
  }

14.3.3 逐步移動

最後就是Next和Prev函數,完成迭代遍歷。這可能會有點繞。下面分別來說明。
首先,在Next移動時,如果當前direction不是kForward的,也就是上一次調用了Prev或者SeekToLast函數,就需要先調整除current之外的所有iterator,爲什麼要做這種調整呢?囉嗦一點,考慮如下的場景,如圖14.3-1所示。

圖14.3-1 Next的移動

當前direction爲kReverse,並且有:Current = memtable Iterator。各Iterator位置爲:{memtable, stable 0, sstable1} ={ key3:1:1, key2:3:1, key2:1:1},這符合prev操作的largest key要求。
注:需要說明下,對於每個update操作,leveldb都會賦予一個全局唯一的sequence號,且是遞增的。例子中的sequence號可理解爲每個key的相對值,後面也是如此。
接下來我們來分析Prev移動的操作。
第一次Prev,current(memtable iterator)移動到key1:3:0上,3者中最大者變成sstable0;因此current修改爲sstable0;
第二次Prev,current(sstable0 Iterator)移動到key1:2:1上,3者中最大者變成sstable1;因此current修改爲sstable1:
此時各Iterator的位置爲{memtable, sstable 0, sstable1} = { key1:3:0, key1:2:1, key2:2:1},並且current=sstable1。
接下來再調用Next,顯然當前Key()爲key2:2:1,綜合考慮3個iterator,兩次Next()的調用結果應該是key2:1:1和key3:1:1。而memtable和sstable0指向的key卻是key1:3:0和key1:2:1,這時就需要調整memtable和sstable0了,使他們都定位到Key()之後,也就是key3:1:1和key2:3:1上。
然後current(current1)Next移動到key2:1:1上。這就是Next時的調整邏輯,同理,對於Prev也有相同的調整邏輯。代碼如下:

virtual void Next() {
    assert(Valid());
    // 確保所有的子Iterator都定位在key()之後.
    // 如果我們在正向移動,對於除current_外的所有子Iterator這點已然成立
    // 因爲current_是最小的子Iterator,並且key() = current_->key()。
    // 否則,我們需要明確設置其它的子Iterator
    if (direction_ != kForward) {
      for (int i = 0; i < n_;i++) { // 把所有current之外的Iterator定位到key()之後
        IteratorWrapper* child =&children_[i];
        if (child != current_) {
          child->Seek(key());
          if (child->Valid()&& comparator_->Compare(key(), child->key()) == 0)
            child->Next(); // key等於current_->key()的,再向後移動一位
        }
      }
      direction_ = kForward;
    }
    // current也向後移一位,然後再查找key最小的Iterator
    current_->Next();
    FindSmallest();
  }

  virtual void Prev() {
    assert(Valid());
    // 確保所有的子Iterator都定位在key()之前.
    // 如果我們在逆向移動,對於除current_外的所有子Iterator這點已然成立
    // 因爲current_是最大的,並且key() = current_->key()
    // 否則,我們需要明確設置其它的子Iterator
    if (direction_ != kReverse) {
      for (int i = 0; i < n_;i++) {
        IteratorWrapper* child =&children_[i];
        if (child != current_) {
          child->Seek(key());
          if (child->Valid()) {
            // child位於>=key()的第一個entry上,prev移動一位到<key()
            child->Prev();
          } else { // child所有的entry都 < key(),直接seek到last即可
           child->SeekToLast();
          }
        }
      }
      direction_ = kReverse;
    }
    //current也向前移一位,然後再查找key最大的Iterator
    current_->Prev();
    FindLargest();
  }

這就是MergingIterator的全部代碼邏輯了,每次Next或者Prev移動時,都要重新遍歷所有的子Iterator以找到key最小或最大的Iterator作爲current_。這就是merge的語義所在了。
但是它沒有考慮到刪除標記等問題,因此直接使用MergingIterator是不能正確的遍歷DB的,這些問題留待給DBIter來解決。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章