首页 > 代码库 > leveldb源码分析--SSTable之TableBuilder

leveldb源码分析--SSTable之TableBuilder

上一篇文章讲述了SSTable的格式以后,本文结合源码解析SSTable是如何生成的。

void TableBuilder::Add(const Slice& key, const Slice& value) {//如果已经插入过数据,那么要保证当前插入的key > 之前最后一次插入的key,// SSTable必须是有序的插入数据if (r->num_entries > 0) {     assert(r->options.comparator->Compare(key, Slice(r->last_key)) > 0);  }//新的block  if (r->pending_index_entry) {//找到前一个block和当前key之间的最短的字符串作为block分界Key,作为块索引    r->options.comparator->FindShortestSeparator(&r->last_key, key);    r->pending_handle.EncodeTo(&handle_encoding);    //将找到的shortest key 和encode后的块索引加入索引块中    r->index_block.Add(r->last_key, Slice(handle_encoding));    r->pending_index_entry = false;  }   //如果使用了filter(leveldb中一般为bloomfilter  if (r->filter_block != NULL) {    r->filter_block->AddKey(key);  }//记录最后一次插入的key,插入数量,添加的数据块中  r->last_key.assign(key.data(), key.size());  r->num_entries++;  r->data_block.Add(key, value);//如果当前已插入的大小达到设定的block阈值,将block写到数据文件中  const size_t estimated_block_size = r->data_block.CurrentSizeEstimate();  if (estimated_block_size >= r->options.block_size) {    Flush();  }}

函数Flush()主要是作一些基本的判断以后调用WriteBlock将数据写入文件并刷到磁盘,然后为下一个block新建一个filter段。仔细看看WriteBlock

void TableBuilder::WriteBlock(BlockBuilder* block, BlockHandle* handle) {//调用block的Finish将block组码为一个内存段,block_builder的内容稍后分析  Slice raw = block->Finish();  // 根据压缩类型压缩  switch (type) {    case kNoCompression:      block_contents = raw;      break;    case kSnappyCompression: {      std::string* compressed = &r->compressed_output;//如果压缩后的大小比原始大小的7/8小(压缩率12.5%以上),则压缩,否则写入原始数据      if (port::Snappy_Compress(raw.data(), raw.size(), compressed) &&          compressed->size() < raw.size() - (raw.size() / 8u)) {        block_contents = *compressed;      } else {        block_contents = raw;        type = kNoCompression;      }      break;    }  }//将内容写入文件,格式为(block_data,type,crc)  WriteRawBlock(block_contents, type, handle);}

Status TableBuilder::Finish() 将当前SSTable的数据写完以后的一些Meta信息的写入,也即数据文件的管理信息的写入,这一部分数据是SSTable中非常关键的一部分数据,我们来看他的写入过程。

Status TableBuilder::Finish() {  //先将data_block内容写到文件  Flush();  // 写filterblock也即为Metablock记录的filter信息,具体分析稍后再filterblock中专门分析  if (ok() && r->filter_block != NULL) {    WriteRawBlock(r->filter_block->Finish(), kNoCompression,                  &filter_block_handle);  }  // 写入metablock的index,包含一个key=filter.filter名,value=http://www.mamicode.com/开始的filter_block的handle  if (ok()) {    BlockBuilder meta_index_block(&r->options);    if (r->filter_block != NULL) {      std::string key = "filter.";      key.append(r->options.filter_policy->Name());      std::string handle_encoding;      filter_block_handle.EncodeTo(&handle_encoding);     meta_index_block.Add(key, handle_encoding);    }    // 写入文件中    WriteBlock(&meta_index_block, &metaindex_block_handle);  }  // 写data_block的索引块,之前每个data_block会取一个FindShortestSeparator作为key,  // value为该block的block_handle,最后一块的key为FindShortSuccessor  if (ok()) {    if (r->pending_index_entry) {      r->options.comparator->FindShortSuccessor(&r->last_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;    }    WriteBlock(&r->index_block, &index_block_handle);  }  // 写footer块,包括了MetaIndex block和Indexblock 的BlockHandle,以及填充区和一个magic数字  if (ok()) {    Footer footer;    footer.set_metaindex_handle(metaindex_block_handle);    footer.set_index_handle(index_block_handle);    footer.EncodeTo(&footer_encoding);    r->status = r->file->Append(footer_encoding);    if (r->status.ok()) {      r->offset += footer_encoding.size();    }  }  return r->status;}

 

知道了table_builder的各个函数的处理流程以后,我们自然会想这些函数是在什么地方调用的呢?什么地方初始化的呢?通过阅读代码我们可以知道build SSTable都是在compaction的时候进行的,为compact memtable和SSTable的时候都会调用到。其中compact memtable是在builder.cc的BuildTable中,而compact SSTable则是在DoCompactionWork中。BuildTable逻辑简单,这里就不再进行解释,推荐读者先阅读这个流程,以认识生成一个SSTable 的过程。而DoCompactionWork由于涉及到更多的compaction相关的内容,这个我们在后面解释compaction的时候再专门介绍。