[leveldb]ldb文件写入

415 阅读7分钟

SStable的结构:

data_block可以有多个,保存的是kv信息,是文件的数据部分

filter_block为过滤数据

metaindex_block为filter_block的index数据,结构同data block只记录了一条数据,key为过滤器名称格式:filter.xxx,value为filter block的size和offset

index_block为data_block的index数据,记录的data_block中每个block的size和offset

footer写在文件尾部,为索引数据的索引,以及文件类型标识

这部分代码实现集中在 table/ 目录

TableBuilder

主要函数实现BuildTable,根据memtable的迭代器,将对应memtable写入磁盘ldb文件

// 输入:iter:new出来的memtable迭代器
//      meta:纪录文件最大key/最小key/文件size
Status BuildTable(const std::string& dbname, Env* env, const Options& options,
                  TableCache* table_cache, Iterator* iter, FileMetaData* meta) {
  Status s;
  meta->file_size = 0;
  iter->SeekToFirst();  // skiplist : list_->head_->Next(0) // next_[0] 头节点指向的level0第一个节点

  std::string fname = TableFileName(dbname, meta->number);     // dbname/xxx.ldb
  if (iter->Valid()) {
    WritableFile* file;
    s = env->NewWritableFile(fname, &file);
    if (!s.ok()) {
      return s;
    }

    TableBuilder* builder = new TableBuilder(options, file);
    meta->smallest.DecodeFrom(iter->key());
    Slice key;
    // 这部分只有data_block直接写了文件
    for (; iter->Valid(); iter->Next()) {  // node_->Next(0), for循环遍历key,写入
      key = iter->key();
      builder->Add(key, iter->value());
    }
    if (!key.empty()) {
      meta->largest.DecodeFrom(key);
    }

    // Finish and check for builder errors
    // 把所有key都flush到ldb文件之后执行(最后一个block的flush在Finish方法里执行)
    // 写入:执行r->filter_block->Finish()并写入过滤数据,不压缩
    //      "filter.xxx" 过滤器名称写入
    //      写入index block
    //      写footer:48字节=两个handle+两个magic number
    s = builder->Finish();
    if (s.ok()) {
      meta->file_size = builder->FileSize();
      assert(meta->file_size > 0);
    }
    delete builder;

    // Finish and check for file errors
    if (s.ok()) {
      s = file->Sync();
    }
    if (s.ok()) {
      s = file->Close();
    }
    delete file;
    file = nullptr;

    if (s.ok()) {
      // Verify that the table is usable
      // 在table_cache创建这个ldb文件对应的迭代器并确认可用
      Iterator* it = table_cache->NewIterator(ReadOptions(), meta->number,
                                              meta->file_size);
      s = it->status();
      delete it;
    }
  }

  // Check for input iterator errors
  if (!iter->status().ok()) {
    s = iter->status();
  }

  if (s.ok() && meta->file_size > 0) {
    // Keep it
  } else {
    env->RemoveFile(fname);   // 有错误,删文件
  }
  return s;
}

TableBuilder::Rep

Rep是TableBuilder唯一的成员变量rep_的类型,保存了用于构建table的一些数据及上下文信息。定义:

// 通过options和WritableFile(ldb文件)构建
// TableBuilder构建时初始化
struct TableBuilder::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 == nullptr? nullptr : new FilterBlockBuilder(opt.filter_policy)),
        pending_index_entry(false) {
    index_block_options.block_restart_interval = 1;
  }

  Options options;  // options用户定义
  Options index_block_options;  // options用户定义
  WritableFile* file;  // dbname/xxx.ldb,写磁盘
  uint64_t offset;  // 初始化为0
  Status status;
  BlockBuilder data_block;    // 使用options用户定义来初始化,data_block大于block_size写入文件后clear
  BlockBuilder index_block;   // 使用options用户定义来初始化,一直在内存中?
  std::string last_key;  // 上一个key,用来校验是否排序好的,以及pending_index_entry相关用来获取上一个block的最后一个key
  int64_t num_entries;  // 初始化0,data_block中kv对的数量
  bool closed;  // Either Finish() or Abandon() has been called.
  FilterBlockBuilder* filter_block;  // filter写入

  // We do not emit the index entry for a block until we have seen the
  // first key for the next data block.  This allows us to use shorter
  // keys in the index block.  For example, consider a block boundary
  // between the keys "the quick brown fox" and "the who".  We can use
  // "the r" as the key for the index block entry since it is >= all
  // entries in the first block and < all entries in subsequent
  // blocks.
  //
  // Invariant: r->pending_index_entry is true only if data_block is empty.
  bool pending_index_entry;  // 初始化为false,只有在data_block是空的时候(Flush之后)才为true,写完文件之后改成true,用于标识是否写index_block
  BlockHandle pending_handle;  // Handle to add to index block

  std::string compressed_output;// 临时保存data_block中数据的snappy压缩结果,也是防止频繁分配内存的复用
};

data_block写文件:

结构

文件中一个data block包含block数据部分、type压缩类型、crc校验三部分

其中数据部分 包括kv部分、多个重启点、以及一个重启点数目

每个重启点纪录的数据部分包含多个kvs,每个kv称之为一个entry,entry编码包含五个部分:key共享长度、key非shared长度、value长度、key非共享内容、value内容

其中,key共享长度为当前key与上一个key的前端相同部分长度;每次新的重启点会把这个长度重置

BlockBuilder

用于构建data_block和index_block的类。定义

class BlockBuilder {
  ...
  const Options* options_;
  std::string buffer_;              // Destination buffer,写入key value的缓冲区
  std::vector<uint32_t> restarts_;  // Restart points,重启点offset集合
  int counter_;                     // Number of entries emitted since restart, 重启点之后的写入entry个数,用来判断是否到了下一个重启点
  bool finished_;                   // Has Finish() been called?
  std::string last_key_; // 上一个key
};

主要函数实现:

Add:向BlockBuilder添加kv
// 功能:将新的key/value写入buffer_,记录重启点等
//      写入key数量到达options_->block_restart_interval之后,restart block,纪录restart时候buffer_的size(重启点位置),并将counter_清0
//      buffer_=公共长度(varint32)+非公共长度+value长度 + key非公共部分字符串 + value字符串
//      更新last_key_
void BlockBuilder::Add(const Slice& key, const Slice& value) {
  Slice last_key_piece(last_key_); // 拷贝了一下,有更改
  assert(!finished_);
  assert(counter_ <= options_->block_restart_interval);
  assert(buffer_.empty()  // No values yet?
         || options_->comparator->Compare(key, last_key_piece) > 0);
  size_t shared = 0;
  if (counter_ < options_->block_restart_interval) {
    // See how much sharing to do with previous string
    // 找last_key_piece和key的公共部分结束位置索引
    const size_t min_length = std::min(last_key_piece.size(), key.size()); 
    while ((shared < min_length) && (last_key_piece[shared] == key[shared])) {
      shared++;
    } // 找last_key_piece和key的公共部分结束位置索引
  } else {
    // Restart compression
    // counter_ == options_->block_restart_interval, 重新计数,增加一个重启点,shared=0
    restarts_.push_back(buffer_.size()); 
    counter_ = 0;
  }
  const size_t non_shared = key.size() - shared;  // 非公共长度

  // Add "<shared><non_shared><value_size>" to buffer_
  // key的公共长度、非公共长度,value长度编码写入buffer_
  PutVarint32(&buffer_, shared);
  PutVarint32(&buffer_, non_shared);
  PutVarint32(&buffer_, value.size());

  // Add string delta to buffer_ followed by value
  buffer_.append(key.data() + shared, non_shared);
  buffer_.append(value.data(), value.size());

  // Update state,这一步操作没看懂为什么,是为了少拷贝shared部分的数据吗?
  // 这个last_key_就是key
  last_key_.resize(shared);
  last_key_.append(key.data() + shared, non_shared);  // 写入非公共部分
  assert(Slice(last_key_) == key);
  counter_++;
}

index_block写文件

每次新开启一个data block,需要向index block写一条数据,记录key、value(size和offset)其中

  • key为上一个block的最后一个last_key与当前block第一个first_key之间的值(last_key, first_key),也有可能是last_key,将会用来查找key所在的block
  • value为pending_handle的值,其中size为上一个data_block的数据部分字节数(不包括type和crc),offset为上一个data_block开始位置

filter_block写文件

每个ldb文件对应一个filter block,filter block文件结构

filter block的compress type为不压缩

每写一个data block会调用一次GenerateFilter函数,将(block filter data+参数k)写入FilterBlockBuilder的result_中,并记录其在result_中的开始位置到filter_offsets_

主要的类定义:

// 用于把一个table的所有filter构建成一个string,存到table的一个block里面
class FilterBlockBuilder {
 public:
  explicit FilterBlockBuilder(const FilterPolicy*);

  FilterBlockBuilder(const FilterBlockBuilder&) = delete;
  FilterBlockBuilder& operator=(const FilterBlockBuilder&) = delete;

  void StartBlock(uint64_t block_offset);
  void AddKey(const Slice& key);
  Slice Finish();

 private:
  // 把所有key从keys_和starts_中解析出来,存到tmp_keys_里面
  // 输入CreateFilter创建过滤器,记录block对应filter的offset
  void GenerateFilter();

  const FilterPolicy* policy_;   // 用户在options里设置的
  std::string keys_;             // Flattened key contents,所有key拼接起来的字符串,写完一个block就clear
  std::vector<size_t> start_;    // Starting index in keys_ of each key, 每个key在keys_中的index
  std::string result_;           // Filter data computed so far
  std::vector<Slice> tmp_keys_;  // policy_->CreateFilter() argument,keys集合,每个block复用的结构
  std::vector<uint32_t> filter_offsets_;   // 每个block的filter对应的offset index
};
BloomFilterPolicy

位置:util/bloom.cc

每个data block对应一个filter entry的数据,格式:

char* arrayk
数据参数,1字节,表示使用的hash函数个数(每个key用的位数)

CreateFilter函数实现

// 输入:keys key集合
  //      n    key数量
  // dst:输出结果
  // 功能:通过hash生成keys对应的bloom过滤器结果dst
  void CreateFilter(const Slice* keys, int n, std::string* dst) const override {
    // Compute bloom filter size (in both bits and bytes)
    // 总共需要的位数 = key个数*每个key的bit数
    size_t bits = n * bits_per_key_;

    // For small n, we can see a very high false positive rate.  Fix it
    // by enforcing a minimum bloom filter length.
    // n很小时正确率很低,通过强制使用最小布隆过滤器长度来修复
    if (bits < 64) bits = 64;

    size_t bytes = (bits + 7) / 8;   // 字节数,向上取整
    bits = bytes * 8;

    const size_t init_size = dst->size();
    dst->resize(init_size + bytes, 0);    // resize并初始化为0
    dst->push_back(static_cast<char>(k_));  // Remember # of probes in filter,最后写入一个字节的k参数
    char* array = &(*dst)[init_size];
    for (int i = 0; i < n; i++) {  // 遍历每个key
      // Use double-hashing to generate a sequence of hash values.
      // See analysis in [Kirsch,Mitzenmacher 2006].
      uint32_t h = BloomHash(keys[i]);
      const uint32_t delta = (h >> 17) | (h << 15);  // Rotate right 17 bits,旋转
      for (size_t j = 0; j < k_; j++) {   // 遍历每个hash函数h
        const uint32_t bitpos = h % bits; // char array的index,第bitpos位
        array[bitpos / 8] |= (1 << (bitpos % 8));  // set 位,如果是0的话设置成1
        h += delta; // 计算第j个hash值
      }
    }
  }

match函数

// 返回:false 不存在这个key
//      true  可能存在这个key
bool KeyMayMatch(const Slice& key, const Slice& bloom_filter)

double-hashing论文:

xueshu.baidu.com/usercenter/…

Footer

footer是放到每个table文件尾部的固定格式

48个char

构成:

1.8字节magic number

2.20字节metaindex handle(uint64 offset + uint64 size)

3.20字节index handle

定义:

 public:
  // Footer编码固定长度=2个BlockHandle和一个magic number的长度
  // magic number是一个固定的数字,用于判断文件类型为ldb(sst)
  enum { kEncodedLength = 2 * BlockHandle::kMaxEncodedLength + 8 };
 private:
  BlockHandle metaindex_handle_;
  BlockHandle index_handle_;

其中blockHandle是存储指向data block或者meta block在文件位置的指针

这个类的size是16,但是编码长度是20