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_); }