leveldb源码解析二——SSTable

news2025/1/17 14:01:16

本章解析leveldb的基本组件——SSTable,SSTable一旦形成,就不会被改变,SSTable的操作有以下2种:
1、构建SSTable,在minor compaction和major compaction时,会构建SSTable,其中minor compaction是从memtable中构建SSTable,major compaction是从多个SSTable merge为一个SSTable
2、读取遍历SSTable,用户查询或者遍历时,会从SSTable中查询对应的key,需要读取SSTable文件内容,并解析到内存中

Block

SSTable由多个Block组成,包括Data Block、Meta Block、Index Block。Data Block存储KV Record,Meta Block存储Block的元数据,目前只有一个BloomFilter,Index Block只有一个,紧挨着Footer,存储Data Block和Meta Block的位置
在这里插入图片描述

Block构建

一个Block中的KV Record分为多组,每组有一个重启点,每组中的key进行前缀压缩,在组内,KV对的存储个数:
共享前缀长度+非共享前缀长度+value长度+key中非共享前缀数据+value数据

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
    // 组中可以继续放置数据
    const size_t min_length = std::min(last_key_piece.size(), key.size());
    while ((shared < min_length) && (last_key_piece[shared] == key[shared])) {
      shared++;
    }
  } else {
    // Restart compression
    restarts_.push_back(buffer_.size());
    counter_ = 0;
  }
  const size_t non_shared = key.size() - shared;

  // Add "<shared><non_shared><value_size>" to 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
  last_key_.resize(shared);
  last_key_.append(key.data() + shared, non_shared);
  assert(Slice(last_key_) == key);
  counter_++;
}

当一个Block构建完成之后,把重启点的信息也编码:

Slice BlockBuilder::Finish() {
  // Append restart array
  for (size_t i = 0; i < restarts_.size(); i++) {
    PutFixed32(&buffer_, restarts_[i]);
  }
  PutFixed32(&buffer_, restarts_.size());
  finished_ = true;
  return Slice(buffer_);
}

Block读取

Block读取时通过Iterator遍历并解析Block中的各个KV Record:

  void Seek(const Slice& target) override {
    // Binary search in restart array to find the last restart point
    // with a key < target
    uint32_t left = 0;
    uint32_t right = num_restarts_ - 1;
    int current_key_compare = 0;

    if (Valid()) {
      // If we're already scanning, use the current position as a starting
      // point. This is beneficial if the key we're seeking to is ahead of the
      // current position.
      // 如果当前以及开始遍历了,那么先用当前的遍历点作为参考点
      current_key_compare = Compare(key_, target);
      if (current_key_compare < 0) {
        // key_ is smaller than target
        left = restart_index_;
      } else if (current_key_compare > 0) {
        right = restart_index_;
      } else {
        // We're seeking to the key we're already at.
        return;
      }
    }

	// KV Record是有序的,二分查找
    while (left < right) {
      uint32_t mid = (left + right + 1) / 2;
      uint32_t region_offset = GetRestartPoint(mid);
      uint32_t shared, non_shared, value_length;
      const char* key_ptr =
          DecodeEntry(data_ + region_offset, data_ + restarts_, &shared,
                      &non_shared, &value_length);
      // 这是起始点出的KV对,所以shared的长度为0
      if (key_ptr == nullptr || (shared != 0)) {
        CorruptionError();
        return;
      }
      Slice mid_key(key_ptr, non_shared);
      if (Compare(mid_key, target) < 0) {
        // Key at "mid" is smaller than "target".  Therefore all
        // blocks before "mid" are uninteresting.
        left = mid;
      } else {
        // Key at "mid" is >= "target".  Therefore all blocks at or
        // after "mid" are uninteresting.
        right = mid - 1;
      }
    }
    
  	bool ParseNextKey() {
    current_ = NextEntryOffset();
    const char* p = data_ + current_;
    const char* limit = data_ + restarts_;  // Restarts come right after data
    if (p >= limit) {
      // No more entries to return.  Mark as invalid.
      current_ = restarts_;
      restart_index_ = num_restarts_;
      return false;
    }

    // Decode next entry
    uint32_t shared, non_shared, value_length;
    // 根据编码格式,解码KV Record的格式
    p = DecodeEntry(p, limit, &shared, &non_shared, &value_length);
    if (p == nullptr || key_.size() < shared) {
      CorruptionError();
      return false;
    } else {
      key_.resize(shared);
      key_.append(p, non_shared);
      value_ = Slice(p + non_shared, value_length);
      while (restart_index_ + 1 < num_restarts_ &&
             GetRestartPoint(restart_index_ + 1) < current_) {
        ++restart_index_;
      }
      return true;
    }
  }

SSTable

Table构建

TableBuilder中包含对应的data_block、index_block、filter_block分别用来构建Data Block、Index Block、Meta Block,其中data_block中的KV Record是对应用户的Key、Value,Meta Block对应的KV Record是filter policy和filter的内容,Index Block的KV Record是每个Block的起始最小的Key和Block的offset+size。

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 index_block_options;
  WritableFile* file;
  uint64_t offset;
  Status status;
  BlockBuilder data_block;
  BlockBuilder index_block;
  std::string last_key;
  int64_t num_entries;
  bool closed;  // Either Finish() or Abandon() has been called.
  FilterBlockBuilder* filter_block;

  // 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;
  BlockHandle pending_handle;  // Handle to add to index block

  std::string compressed_output;
};

添加KV Record:

void TableBuilder::Add(const Slice& key, const Slice& value) {
  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);
  }

  if (r->pending_index_entry) {
  	// 如果已经写满了一个Data Block,则创建新的Data Block
    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;
  }

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

  r->last_key.assign(key.data(), key.size());
  r->num_entries++;
  r->data_block.Add(key, value);

  const size_t estimated_block_size = r->data_block.CurrentSizeEstimate();
  if (estimated_block_size >= r->options.block_size) {
    Flush();
  }
}
Status TableBuilder::Finish() {
  Rep* r = rep_;
  Flush();
  assert(!r->closed);
  r->closed = true;

  BlockHandle filter_block_handle, metaindex_block_handle, index_block_handle;

  // 之前文件中已经写入了各个Data Block
  // Write filter block
  // 写入Filter Block
  if (ok() && r->filter_block != nullptr) {
    WriteRawBlock(r->filter_block->Finish(), kNoCompression,
                  &filter_block_handle);
  }

  // Write metaindex block
  // 写入Meta Index Block
  if (ok()) {
    BlockBuilder meta_index_block(&r->options);
    if (r->filter_block != nullptr) {
      // 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
    WriteBlock(&meta_index_block, &metaindex_block_handle);
  }

  // Write index block
  // 写入Data Index Block
  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);
  }

  // Write footer
  // 最后写入Footer
  if (ok()) {
    Footer footer;
    footer.set_metaindex_handle(metaindex_block_handle);
    footer.set_index_handle(index_block_handle);
    std::string footer_encoding;
    footer.EncodeTo(&footer_encoding);
    r->status = r->file->Append(footer_encoding);
    if (r->status.ok()) {
      r->offset += footer_encoding.size();
    }
  }
  return r->status;
}

Table读取

打开并解析SSTable:

// 打开sstable,并构建成内存中的Table
Status Table::Open(const Options& options, RandomAccessFile* file,
                   uint64_t size, Table** table) {
  *table = nullptr;
  if (size < Footer::kEncodedLength) {
    return Status::Corruption("file is too short to be an sstable");
  }

  // 从文件尾部读取并解析footer
  char footer_space[Footer::kEncodedLength];
  Slice footer_input;
  Status s = file->Read(size - Footer::kEncodedLength, Footer::kEncodedLength,
                        &footer_input, footer_space);
  if (!s.ok()) return s;

  Footer footer;
  s = footer.DecodeFrom(&footer_input);
  if (!s.ok()) return s;

  // Read the index block
  // 读取index block
  BlockContents index_block_contents;
  ReadOptions opt;
  if (options.paranoid_checks) {
    opt.verify_checksums = true;
  }
  s = ReadBlock(file, opt, footer.index_handle(), &index_block_contents);

  if (s.ok()) {
    // We've successfully read the footer and the index block: we're
    // ready to serve requests.
    Block* index_block = new Block(index_block_contents);
    Rep* rep = new Table::Rep;
    rep->options = options;
    rep->file = file;
    rep->metaindex_handle = footer.metaindex_handle();
    rep->index_block = index_block;
    rep->cache_id = (options.block_cache ? options.block_cache->NewId() : 0);
    rep->filter_data = nullptr;
    rep->filter = nullptr;
    *table = new Table(rep);
    (*table)->ReadMeta(footer);
  }

  return s;
}

void Table::ReadMeta(const Footer& footer) {
  if (rep_->options.filter_policy == nullptr) {
    return;  // Do not need any metadata
  }

  // TODO(sanjay): Skip this if footer.metaindex_handle() size indicates
  // it is an empty block.
  ReadOptions opt;
  if (rep_->options.paranoid_checks) {
    opt.verify_checksums = true;
  }
  // 读取meta index block
  BlockContents contents;
  if (!ReadBlock(rep_->file, opt, footer.metaindex_handle(), &contents).ok()) {
    // Do not propagate errors since meta info is not needed for operation
    return;
  }
  Block* meta = new Block(contents);

  // 构建filter
  Iterator* iter = meta->NewIterator(BytewiseComparator());
  std::string key = "filter.";
  key.append(rep_->options.filter_policy->Name());
  iter->Seek(key);
  if (iter->Valid() && iter->key() == Slice(key)) {
    ReadFilter(iter->value());
  }
  delete iter;
  delete meta;
}

key的读取:

Status Table::InternalGet(const ReadOptions& options, const Slice& k, void* arg,
                          void (*handle_result)(void*, const Slice&,
                                                const Slice&)) {
  Status s;
  Iterator* iiter = rep_->index_block->NewIterator(rep_->options.comparator);
  iiter->Seek(k);
  if (iiter->Valid()) {
    // 先跟进index block定位到对应的data block
    Slice handle_value = iiter->value();
    FilterBlockReader* filter = rep_->filter;
    BlockHandle handle;
    // 优先filter查找
    if (filter != nullptr && handle.DecodeFrom(&handle_value).ok() &&
        !filter->KeyMayMatch(handle.offset(), k)) {
      // Not found
    } else {
      // 找到data block之后,再找对应的key
      Iterator* block_iter = BlockReader(this, options, iiter->value());
      block_iter->Seek(k);
      if (block_iter->Valid()) {
        (*handle_result)(arg, block_iter->key(), block_iter->value());
      }
      s = block_iter->status();
      delete block_iter;
    }
  }
  if (s.ok()) {
    s = iiter->status();
  }
  delete iiter;
  return s;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/197918.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Opencv DNN C++ CPU 平台编译配置过程

Opencv DNN C CPU 平台编译配置过程 以下内容基于 windows 平台&#xff0c;实际上不同平台在基础工具齐全的情况下&#xff0c;编译过程差异并不大。 opencv 随着版本的更新&#xff0c;对于不同算子的支持也会逐步完善&#xff0c;所以尽量使用新的版本。 同时也可以把对应…

TCP滑动窗口协议与流量控制

谈到TCP的滑动窗口协议与流量控制&#xff0c;便会想起2006年去华为-3COM&#xff08;现H3C公司时&#xff09;面试时的场景。 当年毕业后&#xff0c;刚刚学了一点TCP的皮毛&#xff0c;仅仅是知道了TCP是面向连接的协议&#xff0c;以对每个报文都进行确认超时重传的机制来保…

摸鱼时间,画个吃豆人玩一下

Ⅰ . 吃豆人小游戏 Canvas API&#xff08;画布&#xff09;是在 HTML5 中新增的标签用于在网页实时生成图像&#xff1b;是一个非常适合&#xff0c;做一些有趣的小游戏 和 动画&#xff1b;下面我们来简单的写一下 这个小例子 &#x1f447; 文章目录Ⅰ . 吃豆人小游戏Ⅱ. 实…

学习嵌入式必读十本书,从C语言到ARM

学习嵌入式必读的十本书籍&#xff0c;按照C语言、数据结构、Linux、C、QT、单片机、ARM的顺序给大家推荐。 01 C语言 凡是计算机、电子、通信、自动化、机械专业的同学&#xff0c;大一的时候必学C语言&#xff0c;而且大部分高校选择的教材都是谭浩强。这本书在网上的评价褒…

【计算机程序设计思想与方法】2 什么是计算思维?

1.2 什么是计算思维? 如《【计算机程序设计思想与方法】1 什么是计算?》中所述,计算是利用计算机一步一步地执行指令来解决问题的过程,计算机科学是关于计算的科学。 正如数学家在证明数学定理时,有独特的数学思维。工程师在设计制造产品时,有独特的工程思维。艺术家在…

【验证码逆向专栏】某验“初代”滑块验证码逆向分析

声明 本文章中所有内容仅供学习交流&#xff0c;抓包内容、敏感网址、数据接口均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff0c;若有侵权&#xff0c;请联系我立即删除&#xff01; 本文章未经许可禁止转载…

【算法】递归

目录1.递归概述2.何时使用递归2.1.定义是递归的2.2.数据结构是递归的2.3.问题的求解方法是递归的3.递归模型4.应用本文参考&#xff1a; 《数据结构教程》第 5 版 李春葆 主编 1.递归概述 &#xff08;1&#xff09;在定义一个过程或函数时&#xff0c;出现直接或者间接调用自…

【微服务】Elasticsearch文档索引库操作(二)

&#x1f697;Es学习第二站~ &#x1f6a9;Es学习起始站&#xff1a;【微服务】Elasticsearch概述&环境搭建(一) &#x1f6a9;本文已收录至专栏&#xff1a;微服务探索之旅 &#x1f44d;希望您能有所收获 一.索引库操作 索引库就类似数据库表&#xff0c;mapping映射就类…

DGIOT低代码场景部门的搭建过程

[小 迪 导读] : 通过低代码页面与konva 大屏的页面设计,围绕部门&#xff0c;实现应用场景快速搭建1.部门创建以及权限分配1.1 打开部门管理页面1.2新增部门1.3 权限分配&#xff0c;点击刚创建的部门&#xff0c;在菜单分配中选择总控台和设备管理(低代码平台会过滤掉非低代码…

Wandb:make visualization better than Tensorboard

Wandb&#xff1a;make visualization better than Tensorboard wandb :一个在线的可多人协作的多功能可视化工具包 我最开始使用的tensorboard&#xff0c;还写了一些相关tensorboard的脚本用于实验。tensorboard这里就不详细介绍了&#xff0c;相信大家都比较了解。直到尝试了…

【MySQL数据库入门】:表的约束

表的约束 真正约束字段的是数据类型&#xff0c;但是数据类型约束很单一&#xff0c;需要有一些额外的约束&#xff0c;更好的保证数据的合法性&#xff0c;从业务 逻辑角度保证数据的正确性。比如有一个字段是email&#xff0c;要求是唯一的。 表的约束很多&#xff0c;这里主…

版本管理之Git

一.版本控制器的方式1.1集中式版本控制工具集中式版本控制工具&#xff0c;版本库是集中存放在中央服务器的&#xff0c;team里每个人work时从中央服务器下载代 码&#xff0c;是必须联网才能工作&#xff0c;局域网或互联网。个人修改后然后提交到中央版本库。 举例&#xff1…

巧用回调函数解决微信小程序与后台数据交互出现的异步问题

问题描述 微信小程序端需要发送一个包含文字与图片的表单数据给后端&#xff0c;我一开始的思路是先上传图片得到临时的URL&#xff0c;后执行POST请求将表单数据发送给后端&#xff0c;但后端只能获取到文字&#xff0c;而图片URL却始终获取不到。 问题原因 注意看我上面的思路…

目标检测研究

传统的目标检测流水线 1.候选区域生成 通过滑动窗口选择感兴趣区域Rol;使用多尺寸的输入图像和多尺度的滑动窗口识别多尺度和不同比例的目标。 ⒉特征向量抽取 常用SIFT、 Harr、HOG、SURF。 3.区域分类 常用支持向量机。 结合集成、串联学习、梯度…

3D俯视角色割草游戏模板+视频教程,免费发布 | 一周精品推荐

大家好&#xff0c;我是晓衡。新年开工第一周&#xff0c;我就被热心的开发者们感动得热泪盈眶&#xff01;今天我冒死推荐几款 Creator 游戏开发资源&#xff0c;希望能对得起这些开发者们&#xff0c;同时也希望你能也有所收获。3D俯视角割草游戏视频源码B 站 UP 主『好巧啊c…

MyBatis 数据查询语句中有关于大于,小于的书写方法 及 查询时相关sql 关键字

前言 提示&#xff1a;这里记录的大概内容&#xff1a; MyBatis 数据查询语句中有关于大于&#xff0c;小于的书写方法 一、MyBatis MyBatis 本是 apache 的一个开源项目 iBatis, 2010 年这个项目由 apache software foundation 迁移到了 google code&#xff0c;并且改名为…

Python封装、继承和多态

Python 语言在设计之初&#xff0c;就定位为一门面向对象的编程语言&#xff0c;“Python 中一切皆对象”。同时&#xff0c;Python 也支持面向对象的三大特征&#xff1a;封装、继承和多态。 一、封装 封装&#xff08;Encapsulation&#xff09;&#xff0c;即在设计类时&am…

讲师邀请 | 在 DevData Talks,开放务实地聊聊研发效能!

什么是 DevData Talks&#xff1f; DevData Talks 是专注于研发效能实践经验与方法论的系列分享活动。 2022 年&#xff0c;我们既看到外部环境变幻莫测&#xff0c;也看到研发效能领域沉下心来稳步发展&#xff0c;从宏大的概念和价值&#xff0c;转向具体的问题&#xff0c…

若依框架代码自动生成器研究-表查询篇

最近生产环境用了一个开源系统&#xff1a;若依&#xff0c;其中有一个版块很有意思&#xff0c;很能提高生产效率: “代码生成器”。 其功能所处模块菜单为&#xff1a;系统工具->代码生成。我们来研究一下他的代码生成逻辑。 工具使用方法 1、建表 使用代码生成&#…

Python列表中你所不知道的事

1. 引言 目前&#xff0c;Python是世界上使用最广泛、最受欢迎的编程语言之一。Python丰富的功能性使它非常流行&#xff0c;因为我们可以使用它创建任何内容。我将在本博客中与大家分享关于Python列表的几条有趣的花絮。 闲话少说&#xff0c;我们直接开始吧&#xff01; 2.…