日期:2014-05-16  浏览次数:20562 次

Leveldb源码分析--8

6 SSTable之2

6.4 创建sstable文件

了解了sstable文件的存储格式,以及Data Block的组织,下面就可以分析如何创建sstable文件了。相关代码在table_builder.h/.cc以及block_builder.h/.cc(构建Block)中。

6.4.1 TableBuilder类

构建sstable文件的类是TableBuilder,该类提供了几个有限的方法可以用来添加k/v对,Flush到文件中等等,它依赖于BlockBuilder来构建Block。

TableBuilder的几个接口说明下:

> void Add(const Slice& key, const Slice& value),向当前正在构建的表添加新的{key, value}对,要求根据Option指定的Comparator,key必须位于所有前面添加的key之后;

> void Flush(),将当前缓存的k/v全部flush到文件中,一个高级方法,大部分的client不需要直接调用该方法;

> void Finish(),结束表的构建,该方法被调用后,将不再会使用传入的WritableFile;

> void Abandon(),结束表的构建,并丢弃当前缓存的内容,该方法被调用后,将不再会使用传入的WritableFile;【只是设置closed为true,无其他操作】

一旦Finish()/Abandon()方法被调用,将不能再次执行Flush或者Add操作。

下面来看看涉及到的类,如图6.3-1所示。

图6.3-1

其中WritableFile和op log一样,使用的都是内存映射文件。Options是一些调用者可设置的选项。

TableBuilder只有一个成员变量Rep* rep_,实际上Rep结构体的成员就是TableBuilder所有的成员变量;这样做的目的,可能是为了隐藏其内部细节。Rep的定义也是在.cc文件中,对外是透明的。

简单解释下成员的含义:

  Options options;   // data block的选项
  Options index_block_options; // index block的选项
  WritableFile* file;  // sstable文件
  uint64_t offset; // 要写入data block在sstable文件中的偏移,初始0
  Status status; //当前状态-初始ok
  BlockBuilder data_block; //当前操作的data block
  BlockBuilder index_block; // sstable的index block
  std::string last_key; //当前data block最后的k/v对的key
  int64_t num_entries; //当前data block的个数,初始0
  bool closed;          //调用了Finish() or Abandon(),初始false
  FilterBlockBuilder*filter_block; //根据filter数据快速定位key是否在block中
  bool pending_index_entry; //见下面的Add函数,初始false
  BlockHandle pending_handle; //添加到index block的data block的信息
  std::string compressed_output;//压缩后的data block,临时存储,写入后即被清空

Filter block是存储的过滤器信息,它会存储{key, 对应data block在sstable的偏移值},不一定是完全精确的,以快速定位给定key是否在data block中。

下面分析如何向sstable中添加k/v对,创建并持久化sstable。其它函数都比较简单,略过。另外对于Abandon,简单设置closed=true即返回。

6.4.2 添加k/v对

这是通过方法Add(constSlice& key, const Slice& value)完成的,没有返回值。下面分析下函数的逻辑:

S1 首先保证文件没有close,也就是没有调用过Finish/Abandon,以及保证当前status是ok的;如果当前有缓存的kv对,保证新加入的key是最大的。

  Rep* r = rep_;
  assert(!r->closed);
  if (!ok()) return;
  if (r->num_entries > 0) {
     assert(r->options.comparator->Compare(key, Slice(r->last_key))> 0);
  }

S2 如果标记r->pending_index_entry为true,表明遇到下一个data block的第一个k/v,根据key调整r->last_key,这是通过Comparator的FindShortestSeparator完成的。

  if (r->pending_index_entry) {
     assert(r->data_block.empty());
     r->options.comparator->FindShortestSeparator(&r->last_key,key);
     std::string handle_encoding;
     r->pending_handle.EncodeTo(&handle_encoding);
     r->index_block.Add(r->last_key, Slice(handle_encoding));
     r->pending_index_entry =false;
  }

接下来将pending_handle加入到index block中{r->last_key, r->pending_handle’sstring}。最后将r->pending_index_entry设置为false。

值得讲讲pending_index_entry这个标记的意义,见代码注释:

直到遇到下一个databock的第一个key时,我们才为上一个datablock生成index entry,这样的好处是:可以为index使用较短的key;比如上一个data block最后一个k/v的key是"the quick brown fox",其后继data block的第一个key是"the who",我们就可以用一个较短的字符串"the r"作为上一个data block的index block entry的key。

简而言之,就是在开始下一个datablock时,Leveldb才将上一个data block加入到index block中。标记pending_index_entry就是干这个用的,对应data block的index entry信息就保存在(BlockHandle)pending_handle。

S3 如果filter_block不为空,就把key加入到filter_block中。

  if (r->filter_block != NULL) {
     r->filter_block->AddKey(key);
  }

S4 设置r->last_key = key,将(key, value)添加到r->data_block中,并更新entry数。

  r->last_key.assign(key.data(), key.size());
  r->num_ent