leveldb源码剖析---迭代器设计

来源:互联网 发布:mac mysql 启动失败 编辑:程序博客网 时间:2024/06/06 08:36

本篇主要介绍leveldb中的迭代器设计。迭代器设计封装了leveldb中的所有遍历操作。一个好的设计思想应该是:每个存在容器的地方,就应该有对应容器的迭代器的设计。leveldb是一个容器,leveldb中的每个level中的所有文件组成一个容器,进行merge时,所以待合并的文件集也是一个容器,当然每个sstable文件本身也是一个容器,等等。因此,为每个容器设计一个迭代器,封装访问容器的细节是一种良好的设计思想,leveldb正是这样做的。


iterator

leveldb中的所有迭代器都继承自iterator虚基类。这个类中包含了所有迭代器的对外接口,如下图所示


这里写图片描述

这里稍微讲一下其中几个接口的定义:
首先应该谨记一点,leveldb中每个容器中的元素都是键值对,所以每个迭代器都指向一个键值对。因此key(),value()函数接口具有萃取迭代器中键值对的作用。

valid()函数接口判断该迭代器是否有效,如果指向键值对就是有效的,否则无效。

SeekToLast()和SeekToFirst()就是将当前迭代器指向容器的结束位置和开始位置。

上面的iterator是一个虚基类,它不和任何实际容器有联系。我们知道,由于迭代器的设计依赖于实际容器的内部结构,因此需要为不同的容器设计不同的迭代器。iterator只不过定义了迭代器的用户接口而已。下面我们看一下leveldb中基于iterator继承而来的迭代器:


这里写图片描述

本篇后面将分别详细介绍这些迭代器的巧妙设计结构。

我们按照自底向上的顺序重点介绍下面这些迭代器

MemTableIterator => ( Iter => TwoLevelIterator ) => MergingIterator => DBIter


class MemTableIterator: public Iterator

这个迭代器是最简单的。因为它所涉及的容器就是跳跃链表。跳跃链表就是一个特殊链表,对它设计迭代器很简单。但是MemTableIterator并不是直接针对跳跃链表的迭代器,它是跳跃链表迭代器的一层薄薄的封装,如下所示

MemTable::Table::Iterator iter_;

同时它的所有接口动作都是直接调用iter_:

 virtual bool Valid() const { return iter_.Valid(); }  virtual void Seek(const Slice& k) { iter_.Seek(EncodeKey(&tmp_, k)); }  virtual void SeekToFirst() { iter_.SeekToFirst(); }  virtual void SeekToLast() { iter_.SeekToLast(); }  virtual void Next() { iter_.Next(); }  virtual void Prev() { iter_.Prev(); }

这里我们可以说明一点,继承iterator的所有迭代器并不能代表leveldb中包含的所有迭代器,它只是说明了针对用户的迭代器,也就是数据库用户可使用的迭代器,是公开接口。像这里的MemTable::Table::Iterator,它并不是针对用户的,而是被程序自身使用的


class Block::Iter : public Iterator

Iter是针对的容器是sstable中的一个block,可以是datablock或者indexblock。结合前面对block结构的解析博文,可以比较容易理解这里的迭代器设计。前面说过,理解一个迭代器的设计不过于理解迭代器所对应的容器的结构,以及迭代器所指向的容器中的元素的结构。

通过前面的博文我们知道,一个block分为两部分,一部分存储经过前缀压缩后的data,一部分存储restart数组。我们看一下iter迭代器中的私有数据成员:

 private:  const Comparator* const comparator_;  const char* const data_;      // 指向存储datablock的内存区域  uint32_t const restarts_;     // restart数组在data_中的偏移  uint32_t const num_restarts_; // restart数组中的元素个数  uint32_t current_; //当前迭代器指向的元素在data_中的偏移  uint32_t restart_index_;  // 当前元素使用的是restart数组中哪个重启点进行恢复  std::string key_;  Slice value_;  Status status_;

上面的注释已经很清楚了。

根据上面的这些数据就可以用迭代器遍历block里面的每个key-value。这个迭代器结合block存储的格式还是很容易理解的。详细再看源码了,这里就不继续讲了。不过需要注意以下几点:

1. 前缀压缩只对key进行压缩,没有对value进行压缩;因为排序本来就是对key大小的排序。
2. restart_index_的作用主要是为了实现迭代器的后退功能。
3. 前缀压缩的时候当前key主要参照的是他/它前面的一个key,而不是起始点的key。


class TwoLevelIterator: public Iterator

顾名思义,这是一个封装了两层迭代器的迭代器。如下图所示


这里写图片描述

也就是说。level1中的迭代器指向的是一个容器,level2中的迭代器才指向真正的元素。所以这是一个二维迭代器模型。

回想一下sstable结构,它核心由两部分组成,分别是index block和data block,其中index block中的每一项指向一个data block。因此这就很适合用前面的二维迭代器模型对sstable设计迭代器。leveldb正是这么做的。

按照惯例,我们看一下TwoLevelIterator的私有数据成员:

  BlockFunction block_function_; //读取block,返回这个block的迭代器  void* arg_;  const ReadOptions options_;  Status status_;  IteratorWrapper index_iter_; //第一层迭代器  IteratorWrapper data_iter_;  //第二层迭代器  std::string data_block_handle_;

注意这里有一个函数指针:BlockFunction 。对于二维迭代器所指向的sstable容器,并不能保证整个容器都存在于内存中。当然,一般每个index block都是存在内存中的,但是可能有部分data block没有在内存中。BlockFunction 的作用就是根据index block的迭代器的信息,从磁盘中读出对应的data block子容器。

这里还需要注意的一点是,两层迭代器都是IteratorWrapper类型而不是iter,这主要是为了缓存key和valid,避免每次都要调用iterator->key()和iterator->valid(),因为这都设计虚函数调用,有一定的性能消耗。


class MergingIterator : public Iterator

这里首先强调一个前面都没有说过的leveldb中迭代器设计的一个原则。
leveldb的迭代器设计必须使得迭代器的next遍历序列的key值是(非严格)递增的

MergingIterator 其实和前面的TwoLevelIterator有点像,他们都是容器之内嵌套子容器。如下图所示


这里写图片描述

它和TwoLevelIterator的区别在于,它没有一层上层迭代器对各个子容器进行管理。在TwoLevelIterator,通过设计一个上层的管理迭代器,使得TwoLevelIterator迭代器的遍历可以自然地符合前面说的递增原则,但是在MergingIterator中,由于没有顶层的总管理迭代器结构,因此,我们不知道应该选择哪个子容器的当前迭代器作为整个容器的当前迭代器。

其实从这里我们可以理解merging的含义,通过一次MergingIterator 的遍历,相当于完成了对多个子容器的归并排序

下面看一下MergingIterator 的私有数据成员:

  const Comparator* comparator_;  IteratorWrapper* children_;  int n_;  IteratorWrapper* current_;  // Which direction is the iterator moving?  enum Direction {    kForward,    kReverse  };  Direction direction_;

children_是一个迭代器数组,数组中的每个元素指向一个子容器当前的迭代器。迭代器的遍历过程就是不断寻找所有子容器当前迭代器所指向的key最小的迭代器的过程。

其余的所有迭代器基本上都是基于以上几种类型的迭代器,这里就不再赘述了


总结

本文较详细地分析了几种类型的迭代器:基于skiplist的MemTableIterator ,基于block容器的iter,基于sstable容器的TwoLeveliter,基于多种容器融合的mergingIter。迭代器作为一种访问容器内元素的手段,它对用户封装了容器的复杂内部结构,给用户展现的是一种类似指针的访问特性。

我们在分析迭代器时,始终秉持着基于容器结构来解剖迭代器。这主要是因为,迭代器本身依赖于容器结构,虽然所有的迭代器都有相似的统一接口,但是它的实现和它所对应的容器密切相关,所以不能脱离容器结构来分析迭代器。

leveldb向我们展示了迭代器设计模式的优雅性。用户在使用迭代器对block,sstable等复杂容器进行访问,遍历时,根本不需要关心容器本身的格式。这体现了面向对象的封装性质。

迭代器于容器的关联一般都体现在迭代器的构造函数中,在构造函数中传入容器

0 0
原创粉丝点击