DBIter

这是一个遍历整个 DB 的迭代器。实际上它内部聚合了一个 MergingIterator iter_,用以包含 Memtable, IMemtable, sstable 的迭代器。 DBIter 是顶级的迭代器,它需要对底层迭代器的结果,状态进行一定的包装。

DBIter 有一个特性如下: 执行完 Prev 后迭代器的saved_key 即是迭代器当前的 user_key,而指针指示的位置是当前 saved_key 节点的上一个节点(Prev 方向)。 这一点不同于 Next,Next 的指针所指位置的 user_key 即是迭代器当前的 user_key。 造成这个不同的关键原因是: Internalkey 排序规则是 Internal key 的排序规则: user_key 正序比较 --> sequence number 逆序 --> type 的逆序 那么相同 user_key 的更新的版本会排在前面(Prev 方向), 所以要找到当前 user_key 的前面一个有效 user_key, 必须要 遍历把 user_key 的所有节点都遍历完, 才会得知这个 user_key` 是否没有被删除, 或是否有更新版本的值.

Next

Next 函数的需求是要定位到下一个有效元素位置。这个接口稍复杂。分两种情况:

  • 1.若当前遍历方向是 kForword,即上一次遍历的方向也是 Next,现在需要定位到下一个有效位置,设当前 user_key 为 saved_key 且 skip=saved_key,我们需要往后遍历,跳过 user_key 等于 skip 的旧版本。 对迭代器执行 Next,对当前位置 current 有如下判断: a).若 valueType 是 deletion,则重新设置 skip=current.user_key,表示向后跳过所有 user_key 等于 skip 的已失效节点,继续 Next。

      注意两点:
      1: skip 被重新设置后一定大于之前的值,因为 Next 遍历到的 user_key 会越来越大。而过程 a) 是递归的:若后面有多个 user_key 的最新版本都是 deletion 则本过程会递归的执行。
      2: skip 被初次设置时表示要跳过 user_key 等于 skip 的旧版本。之后在循环中被设置时表示因为 user_key=skip 的最新版本是 deletion所
      以要跳过后面所有 user_key=skip 的节点。
    

    b).若 valueType 是 value,若 current.user_key <= skip 则继续调用 Next 以跳过当前节点(不论原因是需要跳过旧版本,还是失效节点)。反之则

      结束遍历,终态为:current 已跳过所有 saved_key 的旧版本,以及后面遍历时遇到的其它 user_key 的所有失效节点。当前的 saved_key 为 current.user_key。
    
  • 2.若当前遍历方向是 kReverse,设 savedkey 对应的用户键是 userkey, 但当前迭代器位置指在了最左边(Prev 方向)的那个 user_key 的左边一个位置(Prev 函数的注释中有详细说明)。显然 Next 要定位到后面紧接着的有效元素的位置,我们首先需要调用 iter->Next() 使得 迭代器的位置与当前 user_key 匹配上。之后的处理就与 1 一致了。 我们把之后的处理封装在 FindNextUserEntry 函数中.

/************************************************************************/
/* 
    lzh:
    Next 函数的需求是要定位到下一个有效元素位置。
    若当前遍历方向是 kReverse ,设 saved_key_ 对应的用户键是 user_key, 则当前迭代器位置指在了最左边(Prev 方向)
    的那个 user_key 的左边一个位置(此位置有可能是 deletion, 这会在下一次遍历中处理)(Prev 函数的注释中有详细说明).
    显然 Next 要定位到后面紧接着的有效元素的位置,这就需要跳过 user_key 更旧的版本或失效的版本。
    这在 FindNextUserEntry 会处理.
*/
/************************************************************************/
void DBIter::Next() {
  assert(valid_);

  //lzh: 当前函数 Next 需要向后遍历(kForward), 如果当前的遍历方向是 kReverse, 则需要更改为 kForward
  if (direction_ == kReverse) {  // Switch directions?
    direction_ = kForward;
    // iter_ is pointing just before the entries for this->key(),
    // so advance into the range of entries for this->key() and then
    // use the normal skipping code below.
    if (!iter_->Valid()) {
      iter_->SeekToFirst();//lzh: 注意,leveldb 中所有迭代器失效位置都在最未尾的后面一个位置,当 iter 失效时设置为 First 位置,
                           //lzh: 这是因为上一次遍历的方向是 kReverse,即向 Prev 方向遍历直到 invalid,所以此处 SeekToFirst 是正确的
    } else {
      iter_->Next();
    }
    if (!iter_->Valid()) {
      valid_ = false;
      saved_key_.clear();
      return;
    }
  }

  // Temporarily use saved_key_ as storage for key to skip.
  std::string* skip = &saved_key_;
  SaveKey(ExtractUserKey(iter_->key()), skip);    //lzh: 注意,此处设置了 iter 的 user_key 到 saved_key_ 里面

  //lzh: 跳过 iter_key().user_key_ 更旧的版本和 deleteType 版本
  FindNextUserEntry(true, skip);
}

FindNextUserEntry(bool skipping, std::string* skip)

在 Next 中第 1 个分支已经把这个函数的逻辑讲的差不多了。 该函数耦合了两个功能:

  1. 遍历过程中遇到了某个 user_key 的最新版本是 deletion, 则后面跳过此 user_key 的所有节点。此过程是递归的,即若存在连续这样的 user_key 则全部会被跳过。 -- (skipping 为 true|false 都有此功能)
  2. 跳过 user_key 等于函数参数指定的 skip 的旧版本 -- skipping 为 true 时有此功能

总的来说,skipping 为 true 指示了是否需要向后跳过 skip 指示的 user_key 及已被 delete 的节点。 为 false 指示了只向后跳过最新版本为 delete 的节点直到遇到有效的节点。

在 Next 与 Prev 中调用此函数时都设置 skipping=true,Seek 和 SeekToFirst 设置为 false,这是因为: 考虑 Seek 和 SeekToFirst 的功能, 我们需要定位到的位置: 当它是 deletion, 表明当前 user_key 最新的记录是被删除了, 所以应该继续往后面 seek(即 skip); 当它是 valueType 时, 即使它是一个旧版本的值, 也不应该跳过, 因为有可能调用者就是想定位到旧版本.

考虑 Next 的功能, 我们需要定位到当前版本 user_key 的下一个有效可访问位置. 若下个位置是 deletion 显然也应该跳过, 若是 valueType, 且 user_key 与上一个一致, 但版本较旧, 为了得到 “下一个有效可访问位置”, 我们也需要跳过.

其代码如下:

/************************************************************************/
/* 
lzh: 该函数耦合了两个功能: 
    1. 遍历过程中遇到了某个 user_key 的最新版本是 deletion, 则后面跳过此 user_key 的所有节点。此过程是递归的,即若存在连续这样的 user_key 则全部会被跳过。 -- (skipping 为 true|false 都有此功能)
    2. 跳过 user_key 等于函数参数指定的 skip 的旧版本 -- skipping 为 true 时有此功能

    总的来说,skipping 为 true 指示了是否需要向后跳过 skip 指示的 user_key 及已被 delete 的节点。 为 false 指示了只向后跳过最新版本为 delete 的节点直到遇到有效的节点。

    Internal key 的排序规则: user_key 正序比较 --> sequence number 逆序 --> type 的逆序所以同 user_key 较新的记录在前面被遍历出, 如果先遇到了一个 deleteType 的 InternalKey, 则后面同 user_key 的InternalKey 要被删除.

    传入的参数 skip 可能是 Internalkey, 但此时 skipping 是 false,不影响逻辑(见本文件中 Seek 函数中的调用).个人相信这是原作者的笔误.

    最后注意,如果我们设置 skipping 为 true 且指定 skip 是一个很大的 user_key,则由于两个功能会同时生效,若存在某个 user_key 的最新版本是 deletion 则 skip 会被覆盖
*/
/************************************************************************/
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;
    //lzh: sequence_ 是传入的最新的版本号,只处理小于此版本的数据
    if (ParseKey(&ikey) && ikey.sequence <= sequence_) {
      switch (ikey.type) {
        case kTypeDeletion:
          // Arrange to skip all upcoming entries for this key since
          // they are hidden by this deletion.
            //lzh: 所有位于此 deletion 后面的相同 user_key 都被跳过
          SaveKey(ikey.user_key, skip);
          skipping = true;
          break;
        case kTypeValue:
          if (skipping &&
              user_comparator_->Compare(ikey.user_key, *skip) <= 0) {
                  //lzh: 此处的判断是 <= ,而不仅仅是 =,参考 Seek(const Slice& target) 函数中的注释
          } else {
              //lzh: 如果 skipping=false 或遍历到的 user_key 大于 skip, 表示已经找到了 NextUserEntry ,直接返回
            valid_ = true;
            saved_key_.clear();
            return;
          }
          break;
      }
    }
    iter_->Next();    //lzh: 执行跳过
  } while (iter_->Valid());45                                                                                                                          
  saved_key_.clear();
  valid_ = false;
}

Prev

与 Next 相对, 功能是找到仅仅比当前 user_key 小的有效的 user_key` 节点,但所不同的是,Prev 需要完全遍历完等于 user_key 的所有节点, 才能知道 user_key 节点是不是有效的。

  • 1.若当前遍历方向是 kForword,需要对迭代器循环调用 Prev 以遍历直到遇到不同的 user_key 则停止
  • 2.若当前遍历方向是 kReverse,无需额外处理。 之后统一调用 FindPrevUserEntry:若新的 user_key 的最新版本是 deletion 的则该 user_key 已失效,需要继续 Prev 以找到有效的 user_key
/************************************************************************/
/* 
    lzh: 与 Next 相对, 功能是找到仅仅比当前 user_key 小的有效的 user_key` 节点
        执行完 Prev 后迭代器的指针指示的位置是当前 saved_key 节点的上一个节点. 这一点不同于 Next.
        造成这个不同的关键原因是:
            Internalkey 排序规则是 Internal key 的排序规则: user_key 正序比较 --> sequence number 逆序 --> type 的逆序
        那么相同 user_key 的更新的版本会排在前面(Prev 方向), 所以要找到当前 user_key 的前面一个有效 user_key`, 必须要
        遍历把 user_key` 的所有节点都遍历完, 才会得知这个 user_key` 是否没有被删除, 或是否有更新版本的值. 
*/
/************************************************************************/
void DBIter::Prev() {
  assert(valid_);

  if (direction_ == kForward) {  // Switch directions?
    // iter_ is pointing at the current entry.  Scan backwards until
    // the key changes so we can use the normal reverse scanning code.
    assert(iter_->Valid());  // Otherwise valid_ would have been false

    //lzh: 保存当前的 user_key 到 saved_key_, 等此函数调用完毕, 即成为“上一个 user_key”
    SaveKey(ExtractUserKey(iter_->key()), &saved_key_);
    //lzh: 见函数上的注释: 只有向 Prev 方向遍历直到遇到不同的 user_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) {
            //lzh: 已经跳完了所有相同 user_key 的节点, 可以停止
        break;
      }
    }
    direction_ = kReverse;
  }

  //lzh: 上面的循环已经使迭代器指向了首个小于当前 user_key 的节点, 现在调用 FindPrevUserEntry 是为了跳过
  //lzh; 失效的节点
  FindPrevUserEntry();
}

FindPrevUserEntry

该函数无参,与上一个不同。根本上来讲,此函数的逻辑较简单,那就是跳过无效的 user_key:我们只需要往前遍历,直到遍历到另外一个 user_key`,然后再回头看 user_key 最后一次被遍历到的节点是不是 deletion 节点。所以该函数不需要参数,直接按此逻辑便是。

/************************************************************************/
/* 
    lzh: 本函数耦合两个功能:
    1. 向前移动当前迭代器的位置, 使其指示到更小的一个 user_key 的节点.(此节点可能是 deletion)
    2. saved_key_ 需要指示出之前一个有效的 user_key`. (暗示着, 如果之前一个节点是 deletion, 则需要继续往前遍历).
        此过程是递归的, 如果往前移动的过程中, 遇到的 user_key 的最新版本总是 deletion 则始终需要往前移动.

    设当前迭代器指示着 user_key` 的某个节点, 往前遍历遇到它的最新版本, 但是类型是 deletion, 则遍历到的所有 user_key` 节点
    都不再是有效的, 所以需要继续往前遍历. 这也是函数里面这段判断代码的意义: 
        if ((value_type != kTypeDeletion) &&    //上一节点的类型不能是 deletion
            user_comparator_->Compare(ikey.user_key, saved_key_) < 0)    //遇到了更小的 user_key
    举例说明:
    设 user_key: B < A, 且有如下操作流程: A.add    -->    A.add    -->    A.delete    -->    B.add --> B.delete, 则迭代器依次遍历的顺序是:(简单地以链表形式去表示)
    (提示: Internal key 的排序规则: user_key 正序比较 --> sequence number 逆序 --> type 的逆序):

    1(B.delete)    -->    2(B.add)    -->    3(A.delete)    -->    4(A.add)    -->    5(A.add)

    若当前迭代器位置指向 5, 则 FindPrevUserEntry 依次遍历的节点是 4, 3, 2, 1
    遍历到 2 时, 由于之前节点的类型是 deletion 则不应该 break, 继续遍历到 1, 再因为 !valid() 返回. 

    以下细节需要注意:
        1. 正常情况下函数结束时迭代器指向的位置的后一个节点不可能是 deletion, 当且仅当整个迭代器遍历到了尽头.
        2. 函数结束时迭代器所指位置可能是一个 deletion节点(这种情况下,形成这个局面的原因是做了空的 delete).举例如下:
            0(A.add)    -->    1(A.delete)    -->    2(B.add)
            当前迭代器指向 2 时, 往前遍历, 则到 1 节点时会 break. 此时迭代器会指在 1,且 saved_key_ 会保存着 2 节点的值.
            仔细观察上面的数据, 操作序列是: 增加B, 删除A, 增加A. 实际上,不管之前的数据(user_key) 有没有 A,“删除A”这个操作
            是无意义的.

*/
/************************************************************************/
void DBIter::FindPrevUserEntry() {
  assert(direction_ == kReverse);

  ValueType value_type = kTypeDeletion;
  if (iter_->Valid()) {
    do {
      ParsedInternalKey ikey;
      if (ParseKey(&ikey) && ikey.sequence <= sequence_) {
        if ((value_type != kTypeDeletion) &&
            user_comparator_->Compare(ikey.user_key, saved_key_) < 0) {
                //lzh: value_type 是上一次遍历到的节点的类型, 
                //lzh: 当且仅当上一节点不是 deletion(也即上一个 user_key 的最新版本不是 deletion) 且当前节点是一个新的 user_key 时跳出
                //lzh: 注意当前节点有可能是 deletion
          // We encountered a non-deleted value in entries for previous keys,
          break;
        }
        //lzh: 注意, 在此之前 value_type 指的是上一次遍历到的 Internalkey 的 type
        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());
  }

  //lzh: 注意上面的 break 时,value_type!=kTypeDeletion,此时 value_type 并未被重新赋值
  //lzh: 所以此分支表示整个迭代器遍历完了,但是首个节点的 value_type 是 kTypeDeletion,所以置迭代器状态为 invalid
  if (value_type == kTypeDeletion) {
    // End
    valid_ = false;
    saved_key_.clear();
    ClearSavedValue();
    direction_ = kForward;
  } else {
    valid_ = true;
  }
}

Seek(const Slice& target)

这是一个定位函数,不用考虑上一次的遍历方向是 Next 还是 Prev,我们只需要对迭代器定位到 target 位置即可。当然,我们需要防止定位到了 deletion 位置。 SeekToFirst 与此类似,不再缀述。

/************************************************************************/
/* 
    lzh: Seek 调用 FindNextUserEntry 传入 skipping=false, 这点和 SeekToFirst一样, 
        但是和 Next 不一样, Next 传入的是 true.
        原因如下: 
        考虑 Seek 和 SeekToFirst 的功能, 我们需要定位到的位置: 
            当它是 deletion, 表明当前 user_key 最新的记录是被删除了, 所以应该继续往后面 seek(即 skip); 
            当它是 valueType 时, 即使它是一个旧版本的值, 也不应该跳过, 因为有可能调用者就是想定位到旧版本.

        考虑 Next 的功能, 我们需要定位到当前版本 user_key 的下一个有效可访问位置. 若下个位置是 deletion 显然也应该跳过, 
        若是 valueType, 且 user_key 与上一个一致, 但版本较旧, 为了得到 “下一个有效可访问位置”, 我们也需要跳过.
*/
/************************************************************************/
void DBIter::Seek(const Slice& target) {
  direction_ = kForward;
  ClearSavedValue();
  saved_key_.clear();
  AppendInternalKey(
      &saved_key_, ParsedInternalKey(target, sequence_, kValueTypeForSeek));
  iter_->Seek(saved_key_);

  //lzh: 执行 iter_->Seek(saved_key_) 后,有可能 iter_ 指向的是一个 user_key 大于 target 的位置(即 target 不存在),
  //lzh: 此时 iter_.user_key > saved_key.user_kty,FindNextUserEntry 中的判断 <= 就有意义了,而不仅仅是 ==

  if (iter_->Valid()) {

    //lzh: 此时传入的 saved_key_ 是 Internalkey, 仅此一处。个人认为可能是笔误。但调用完 FindNextUserEntry 函数后, saved_key 被清空
    FindNextUserEntry(false, &saved_key_ /* temporary storage */);
  } else {
    valid_ = false;
  }
}

void DBIter::SeekToFirst() {
  direction_ = kForward;
  ClearSavedValue();
  iter_->SeekToFirst();
  if (iter_->Valid()) {
      //lzh: 当且仅当 iter_key() 已经是一个 deletion 时跳过它后面相同 user_key.
    FindNextUserEntry(false, &saved_key_ /* temporary storage */);
  } else {
    valid_ = false;
  }

SeekToLast

与 SeekToFirst 不同,此函数带有“Prev”属性,所以迭代器所指的位置应是当前值的左边(Prev 方向)的节点。同时为了保证当前值不是 deletion, 需要在执行 seek 之后调用 FindPrevUserEntry 以让指针再向前指到 saved_key 的左边一个位置。

void DBIter::SeekToLast() {
  direction_ = kReverse;
  ClearSavedValue();
  iter_->SeekToLast();

  ParsedInternalKey ikey;
  ParseKey(&ikey);

  FindPrevUserEntry();
}

如何遍历整个 levelDB

db_impl.cc 中生成整个数据库遍历的 Iterator 实现代码如下,比较易懂。不再缀述。

Iterator* DBImpl::NewInternalIterator(const ReadOptions& options,
                                      SequenceNumber* latest_snapshot) {
  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);

  mutex_.Unlock();
  return internal_iter;
}

.........

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

results matching ""

    No results matching ""