Leveldb源碼解析第三篇【sstable 收尾】

版權聲明:本文爲博主原創文章,未經博主允許不得轉載。

前面介紹完了table的data block和filter block,今天就來講table收一下尾,table還剩meta index block,index block,footer

這幾個都比較簡單,就一起介紹了

table.h中是用來解析一個table的,在沒搞懂table是什麼東西前直接解析有點困難,而瞭解table最快速的辦法就是看怎麼table怎麼構造的,下面直接開始構造一個table,開整

本章涉及到源碼文件有

include/leveldb/table.h
include/leveldb/table_builder.h
table/table.cc
table/table_builder.cc
table/format.h
// table_builder.h
class TableBuilder {
 public:
  // 構造方法
  TableBuilder(const Options& options, WritableFile* file);

  // 析構方法
  ~TableBuilder();

  // 改變配置參數
  Status ChangeOptions(const Options& options);

  // 在 table 中添加一個key
  void Add(const Slice& key, const Slice& value);

  // 當一個data block達到閾值以後調用
  void Flush();

  // Return non-ok iff some error has been detected.
  Status status() const;

  // 當table大小達到閾值後調用,主要是講meta block,index block等信息寫入到文件中
  Status Finish();

  // Indicate that the contents of this builder should be abandoned.  Stops
  // using the file passed to the constructor after this function returns.
  // If the caller is not going to call Finish(), it must call Abandon()
  // before destroying this builder.
  // REQUIRES: Finish(), Abandon() have not been called
  void Abandon();

  // 返回有多少個鍵值對
  uint64_t NumEntries() const;

  // 返回文件大小
  uint64_t FileSize() const;

 private:
  // 返回操作是否成功
  bool ok() const { return status().ok(); }
  // 將block寫入到編碼到string中
  void WriteBlock(BlockBuilder* block, BlockHandle* handle);
  // 將編碼後的block寫入到文件中
  void WriteRawBlock(const Slice& data, CompressionType, BlockHandle* handle);

  // rep很重要,table的所有信息都放在這個結構體中,現在還不知道爲什麼這麼設計,
  // 後面深入後在分析作者這麼設計的目錄
  struct Rep;
  Rep* rep_;

  // No copying allowed
  TableBuilder(const TableBuilder&);
  void operator=(const TableBuilder&);
};
// table_builder.cc
// 先從存放table所有信息的結構體開始
struct TableBuilder::Rep {
  // 兩個配置對象,暫時可以忽略
  Options options;
  Options index_block_options;
  // 最後table是要寫入文件的,file對象就是用來幹這個事的
  WritableFile* file;
  // offset記錄table的偏移量,主要用於index block,後續會介紹
  uint64_t offset;
  // 每次操作的狀態
  Status status;
  // 存放data block
  BlockBuilder data_block;
  // 存放index block
  BlockBuilder index_block;
  // 最後一個key
  std::string last_key;
  // table中有多個鍵值對
  int64_t num_entries;
  // table是否結束
  bool closed;          // Either Finish() or Abandon() has been called.
  // 上一章講到的
  FilterBlockBuilder* filter_block;

  // 這個變量表示添加的key是不是data block的第一個key
  bool pending_index_entry;
  BlockHandle pending_handle;  // Handle to add to index block
  // data block添加到table中時會壓縮block,這個變量是用來存儲壓縮後的內容
  std::string compressed_output;

  // rep構造方法
  Rep(const Options& opt, WritableFile* f)
      : options(opt),
        index_block_options(opt),
        file(f),
        offset(0),
        data_block(&options),
        index_block(&index_block_options),
        num_entries(0),
        closed(false),
        filter_block(opt.filter_policy == NULL ? NULL
                     : new FilterBlockBuilder(opt.filter_policy)),
        pending_index_entry(false) {
    index_block_options.block_restart_interval = 1;
  }
};

// 在table中添加一個key-value,也就是在table中的data block中添加一個key
void TableBuilder::Add(const Slice& key, const Slice& value) {
  // table的所有信息全部封裝在了rep_中
  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);
  }
  // pending_index_entry表示添加的key是不是data block的第一個key
  if (r->pending_index_entry) {
    assert(r->data_block.empty());
    // 如果是的話,就要先得到上一個data block的最後一個key和當前data block的第一個key的一箇中間key
    // 比如last_key爲abcd,key爲abcg,那麼中間key爲abce,也就是第一個不同的字符加1
    r->options.comparator->FindShortestSeparator(&r->last_key, key);
    // handle_encoding保留上一個data block的偏移信息
    std::string handle_encoding;
    // 將上一個data block的偏移信息編碼到string中
    r->pending_handle.EncodeTo(&handle_encoding);
    // 在index_block中添加中間key和上一個data block的偏移信息
    r->index_block.Add(r->last_key, Slice(handle_encoding));
    // pending_index_entry設置爲false,表示後面在添加key,就不是第一個key了

    r->pending_index_entry = false;
  }

  if (r->filter_block != NULL) {
    // 在filter block的keys中添加key
    r->filter_block->AddKey(key);
  }
  // 給last_key賦上新值
  r->last_key.assign(key.data(), key.size());
  r->num_entries++;
  // 在data block中添加鍵值
  r->data_block.Add(key, value);
  // 得到當前data block的估值大小
  const size_t estimated_block_size = r->data_block.CurrentSizeEstimate();
  if (estimated_block_size >= r->options.block_size) {
    // 如果大於閾值的話就要刷新了
    Flush();
  }
}

// 當一個data block結束時,就會刷新一下
void TableBuilder::Flush() {
  Rep* r = rep_;
  assert(!r->closed);
  if (!ok()) return;
  if (r->data_block.empty()) return;
  assert(!r->pending_index_entry);
  // 將data block寫入到文件中
  WriteBlock(&r->data_block, &r->pending_handle);
  if (ok()) {
    // 爲下一個data block作準備
    r->pending_index_entry = true;
    r->status = r->file->Flush();
  }
  if (r->filter_block != NULL) {
    r->filter_block->StartBlock(r->offset);
  }
}

void TableBuilder::WriteBlock(BlockBuilder* block, BlockHandle* handle) {
  // File format contains a sequence of blocks where each block has:
  //    block_data: uint8[n]
  //    type: uint8
  //    crc: uint32
  assert(ok());
  Rep* r = rep_;
  // 當前得到的block裏面只是存儲了key-value,真正完成一個block還需要在後面寫入重啓點等信息
  raw是整個block(包括重啓點)放到string裏面的信息
  Slice raw = block->Finish();

  Slice block_contents;
  // data block是否需要壓縮
  CompressionType type = r->options.compression;
  // TODO(postrelease): Support more compression options: zlib?
  switch (type) {
    case kNoCompression:
    // 不需要壓縮的話直接向block的內容賦值給block_contents
      block_contents = raw;
      break;

    case kSnappyCompression: {
      // 如果需要壓縮的話,會將壓縮後的信息放到compressed
      std::string* compressed = &r->compressed_output;
      // 如果需要壓縮,壓縮率高於12.5%的話就壓縮
      if (port::Snappy_Compress(raw.data(), raw.size(), compressed) &&
          compressed->size() < raw.size() - (raw.size() / 8u)) {
        block_contents = *compressed;
      } else {
        // 否則就直接不壓縮了,並將type設置爲不壓縮
        // Snappy not supported, or compressed less than 12.5%, so just
        // store uncompressed form
        block_contents = raw;
        type = kNoCompression;
      }
      break;
    }
  }
  // 將block的內容放到table中
  WriteRawBlock(block_contents, type, handle);
  // 將壓縮後的內容設置爲空
  r->compressed_output.clear();
  // block也需要重置
  block->Reset();
}

void TableBuilder::WriteRawBlock(const Slice& block_contents,
                                 CompressionType type,
                                 BlockHandle* handle) {
  Rep* r = rep_;
  // 將當前block的偏移和block的內容放到handle中
  handle->set_offset(r->offset);
  handle->set_size(block_contents.size());
  // 將block的內容寫入到文件中
  r->status = r->file->Append(block_contents);
  if (r->status.ok()) {
    // block的結尾標識,有5個字符
    char trailer[kBlockTrailerSize];
    // 第一個字符記錄的是否壓縮
    trailer[0] = type;
    // 算出block的crc值
    uint32_t crc = crc32c::Value(block_contents.data(), block_contents.size());
    crc = crc32c::Extend(crc, trailer, 1);  // Extend crc to cover block type
    // 將crc值放到後4個字符中
    EncodeFixed32(trailer+1, crc32c::Mask(crc));
    // 將結尾標識放到block後面
    r->status = r->file->Append(Slice(trailer, kBlockTrailerSize));
    if (r->status.ok()) {
      // r的偏移就要加上block的大小和結尾標識的大小了
      r->offset += block_contents.size() + kBlockTrailerSize;
    }
  }
}

Status TableBuilder::status() const {
  return rep_->status;
}


// 上述代碼中都只是在文件中寫入data block的內容,當table結束時就需要寫入filter,index等信息了
Status TableBuilder::Finish() {
  Rep* r = rep_;
  Flush();
  assert(!r->closed);
  r->closed = true;
  // meta block就是filter block
  BlockHandle filter_block_handle, metaindex_block_handle, index_block_handle;

  // Write filter block
  // 將filter block不壓縮,寫入到文件中
  if (ok() && r->filter_block != NULL) {
    WriteRawBlock(r->filter_block->Finish(), kNoCompression,
                  &filter_block_handle);
  }

  // Write metaindex block
  // meta index block只存了一個key-value,key是filter使用的算法名字,value是filter block的偏移信息
  // 作用是在table中快速定位到filter block
  if (ok()) {
    BlockBuilder meta_index_block(&r->options);
    if (r->filter_block != NULL) {
      // Add mapping from "filter.Name" to location of filter data
      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);
    }

    // TODO(postrelease): Add stats and other meta blocks
    // 將meta index block寫入到file中
    WriteBlock(&meta_index_block, &metaindex_block_handle);
  }

  // Write index block
  // 
  if (ok()) {
    if (r->pending_index_entry) {
      // 如果最後一個key是最後data block的第一個key的話,需要將這個block的偏移寫到index block中
      // 和Add函數類似
      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;
    }
    // 將index block寫入到文件中
    WriteBlock(&r->index_block, &index_block_handle);
  }

  // Write footer
  if (ok()) {
    // footer裏面記錄了index block和meta index block的偏移信息
    Footer footer;
    // 將meta index block的偏移信息放在footer中
    footer.set_metaindex_handle(metaindex_block_handle);
    // 將index block的偏移信息放在footer中
    footer.set_index_handle(index_block_handle);
    std::string footer_encoding;
    // 將footer信息編碼到string中
    footer.EncodeTo(&footer_encoding);
    // 然後將footer_encoding寫入到文件中
    r->status = r->file->Append(footer_encoding);
    if (r->status.ok()) {
      r->offset += footer_encoding.size();
    }
  }
  return r->status;
}

【作者:antonyxu https://antonyxux.github.io/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章