[LevelDB]Block系统内幕解析-元数据块(Meta Block)元数据索引块(MetaIndex Block)索引块(Index Block)

news2025/4/18 21:06:43

本文内容组织形式

  • Block的基本信息
  • 作用
    • 示意图
    • 举例说明
  • 源码解析
    • Footer
      • 格式
      • 写入&读取
      • 编码&解码
    • 元数据块(Meta Block)
      • 构建&读取
    • 元数据索引块
      • 构建&读取
    • 索引块
      • 定义
      • 构建&读取
      • 核心方法-FindShortestSeparator&FindShortSuccessor
        • 作用:
        • 举例说明
        • FindShortestSeparator
        • FindShortSuccessor
  • 猜你喜欢
  • PS

Block的基本信息

1
Block 是组成SSTable文件中的基本单元,主要有以下类型

  1. 数据块(Data Block):存储实际的键值对数据,按键排序并使用前缀压缩减少空间占用。
  2. 过滤块(Filter Block):包含布隆过滤器,用于快速判断一个键是否可能存在,避免不必要的磁盘读取。
  3. 元数据块(Meta Block):存储关于SSTable文件的额外元数据信息,如统计数据或特定功能的配置。
  4. 元数据索引块(Metaindex Block):保存指向各个元数据块的索引,方便查找特定类型的元数据。
  5. 索引块(Index Block):存储数据块的索引信息,记录每个数据块的最大键和偏移量,用于定位特定键所在的数据块。

作用

这里的元数据块,元数据索引块,索引块,本质上就是在做加速检索的事情,接下来我们先说说这些索引块在数据检索中的实际作用

示意图

在这里插入图片描述
数据访问流程:
① 从Footer获取索引块位置 → ② 获取元数据索引块位置(可选) → ③ 检查元数据(可选) → ④ 定位并读取目标数据

举例说明

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

源码解析

Footer

Footer 重点作用就是存位置信息

  1. 元数据索引块位置信息
  2. 数据索引块位置信息

格式

class Footer {
 public:
  enum { kEncodedLength = 2 * BlockHandle::kMaxEncodedLength + 8 }; // Footer的固定长度
  Footer() = default;

  const BlockHandle& metaindex_handle() const { return metaindex_handle_; }
  void set_metaindex_handle(const BlockHandle& h) { metaindex_handle_ = h; }

  const BlockHandle& index_handle() const { return index_handle_; }
  void set_index_handle(const BlockHandle& h) { index_handle_ = h; }

  void EncodeTo(std::string* dst) const;
  Status DecodeFrom(Slice* input);

 private:
  BlockHandle metaindex_handle_;  // 元数据索引块句柄
  BlockHandle index_handle_;      // 数据索引块句柄
};

写入&读取

Status TableBuilder::Finish() {
 ...
  // Write footer
  if (ok()) {
    Footer footer;
    // handle 是对应位置的信息
    footer.set_metaindex_handle(metaindex_block_handle);
    footer.set_index_handle(index_block_handle);
    std::string footer_encoding;
    // 对footer的信息进行编码
    footer.EncodeTo(&footer_encoding);
    // 进行写入逻辑
    r->status = r->file->Append(footer_encoding);
    if (r->status.ok()) {
      r->offset += footer_encoding.size();
    }
  }
 ...
}
tatus Table::Open(const Options& options, RandomAccessFile* file,
                   uint64_t size, Table** table) {
  if (size < Footer::kEncodedLength) {
    return Status::Corruption("file is too short to be an sstable");
  }
  
  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;
  // ...
}

编码&解码

使用魔数 来进行校验当前的数据

void Footer::EncodeTo(std::string* dst) const {
  const size_t original_size = dst->size();
  metaindex_handle_.EncodeTo(dst);
  index_handle_.EncodeTo(dst);
  dst->resize(2 * BlockHandle::kMaxEncodedLength);  // 填充
  // 写入魔数 用作验证
  PutFixed32(dst, static_cast<uint32_t>(kTableMagicNumber & 0xffffffffu));
  PutFixed32(dst, static_cast<uint32_t>(kTableMagicNumber >> 32));
  assert(dst->size() == original_size + kEncodedLength);
}
Status Footer::DecodeFrom(Slice* input) {
  const char* magic_ptr = input->data() + kEncodedLength - 8;
  const uint32_t magic_lo = DecodeFixed32(magic_ptr);
  const uint32_t magic_hi = DecodeFixed32(magic_ptr + 4);
  const uint64_t magic = ((static_cast<uint64_t>(magic_hi) << 32) |
                         (static_cast<uint64_t>(magic_lo)));
  if (magic != kTableMagicNumber) {
    return Status::Corruption("not an sstable (bad magic number)");
  }

  Status result = metaindex_handle_.DecodeFrom(input);
  if (result.ok()) {
    result = index_handle_.DecodeFrom(input);
  }
  if (result.ok()) {
    const char* end = magic_ptr + 8;
    *input = Slice(end, input->data() + input->size() - end);
  }
  return result;
}

元数据块(Meta Block)

元数据块 主要用来存储过滤块的信息, 在当前版本的levelDB中等同于filter block,用来帮助当前sstable文件快速检索。

PS: 具体filter_block的信息, 可参考:https://editor.csdn.net/md/?articleId=147023029

构建&读取

// Write metaindex 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);
}
void Table::ReadMeta(const Footer& footer) {
  if (rep_->options.filter_policy == nullptr) {
    return;
  }

  ReadOptions opt;
  if (rep_->options.paranoid_checks) {
    opt.verify_checksums = true;
  }
  BlockContents contents;
  // 读取当前文件的元数据信息
  if (!ReadBlock(rep_->file, opt, footer.metaindex_handle(), &contents).ok()) {
    return;
  }

  Block* meta = new Block(contents);
  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;
}

元数据索引块

构建&读取

Status TableBuilder::Finish() {
  ...
  // 写入元数据索引块
  if (ok()) {
    BlockBuilder meta_index_block(&r->options);
    if (r->filter_block != nullptr) {
      // 添加过滤器的位置信息到元数据索引,这里只添加索引位置信息
      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);// 这里添加元数据索引信息和具体的元数据信息
  }
...
}
Status Table::Open(const Options& options, RandomAccessFile* file,
                   uint64_t size, Table** table) {
  // ... 读取Footer
  
  // 读取索引块
  BlockContents index_block_contents;
  s = ReadBlock(file, opt, footer.index_handle(), &index_block_contents);// 
  
  if (s.ok()) {
    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;
    // ... 其他初始化
    
    *table = new Table(rep);
    (*table)->ReadMeta(footer);  // 读取元数据
  }
}
// 读取元数据
void Table::ReadMeta(const Footer& footer) {
  if (rep_->options.filter_policy == nullptr) {
    return;
  }

  ReadOptions opt;
  BlockContents contents;
  // 这里是读取具体的元数据索引信息
  if (!ReadBlock(rep_->file, opt, footer.metaindex_handle(), &contents).ok()) {
    return;
  }

  Block* meta = new Block(contents);
  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;
}

索引块

定义

class BlockBuilder {
 private:
  const Options* options_;          // 配置选项
  std::string buffer_;             // 存储实际数据
  std::vector<uint32_t> restarts_; // 重启点数组
  int counter_;                    // 计数器
  bool finished_;                  // 是否已完成
  std::string last_key_;          // 上一个键
};
struct TableBuilder::Rep {
  Rep(const Options& opt, WritableFile* f)
      : options(opt),
        index_block_options(opt), // 索引块选项
        // ... 其他初始化
        index_block(&index_block_options), // 创建索引块
        // ...
  {
    index_block_options.block_restart_interval = 1; // 索引块的重启点间隔为1
  }
  // ...
  BlockBuilder index_block;  // 索引块构建器
};

构建&读取

void TableBuilder::Add(const Slice& key, const Slice& value) {
  Rep* r = rep_;
  // 处理待处理的索引条目
  if (r->pending_index_entry) {
    assert(r->data_block.empty());
    // 找到最短分隔符作为索引键
    r->options.comparator->FindShortestSeparator(&r->last_key, 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;
  }
  // ... 其他处理
}
Status TableBuilder::Finish() {
  // ... 其他处理
  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等
}

核心方法-FindShortestSeparator&FindShortSuccessor

作用:
  1. 为每个数据块找到一个最短的键,该键大于当前块中的所有键,但小于下一个块中的所有键
  2. 通过这种方式可以减少索引块的大小,因为不需要存储完整的键
举例说明

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

FindShortestSeparator

简单说下这个逻辑,本质上就是找到第一个出现diff index的字符然后生成自己的索引字符

/**
第一步找到第一个不同的字符位置。这是必要的,因为:
分隔符必须保持共同前缀不变(否则可能会小于start)
只能在第一个不同的位置上做修改
**/
 void FindShortestSeparator(std::string* start,
                             const Slice& limit) const override {
    // Find length of common prefix
    // 找到 start 和limit 之间的最短分隔符
    size_t min_length = std::min(start->size(), limit.size());
    size_t diff_index = 0;
    while ((diff_index < min_length) &&
           ((*start)[diff_index] == limit[diff_index])) {
      diff_index++;
    }
/**
然后对第一个出现diff的区域+1 (ps: 当 < oxff 时,担心出现溢出问题)
**/
    if (diff_index >= min_length) {
      // Do not shorten if one string is a prefix of the other
    } else {
      uint8_t diff_byte = static_cast<uint8_t>((*start)[diff_index]);
      // < oxff 的原因 是因为担心 +1 出现溢出问题
      if (diff_byte < static_cast<uint8_t>(0xff) &&
          diff_byte + 1 < static_cast<uint8_t>(limit[diff_index])) {
        (*start)[diff_index]++;
        start->resize(diff_index + 1);
        assert(Compare(*start, limit) < 0);
      }
    }
  }
FindShortSuccessor

这个函数很简单: 找到一个比当前key大的最小字符串

  void FindShortSuccessor(std::string* key) const override {// 用于找到  key的下一个字典序最小的字符串
    // Find first character that can be incremented
    size_t n = key->size();
    for (size_t i = 0; i < n; i++) {
      const uint8_t byte = (*key)[i];
      // 找到第一个不是0xFF的字符
      if (byte != static_cast<uint8_t>(0xff)) 
       // 将这个字符加1
       {
        (*key)[i] = byte + 1;
        // 截断后面的所有字符
        key->resize(i + 1);
        return;
      }
    }
    // *key is a run of 0xffs.  Leave it alone.
  }

猜你喜欢

C++多线程: https://blog.csdn.net/luog_aiyu/article/details/145548529
一文了解LevelDB数据库读取流程:https://blog.csdn.net/luog_aiyu/article/details/145946636
一文了解LevelDB数据库写入流程:https://blog.csdn.net/luog_aiyu/article/details/145917173
关于LevelDB存储架构到底怎么设计的:https://blog.csdn.net/luog_aiyu/article/details/145965328?spm=1001.2014.3001.5502

PS

你的赞是我很大的鼓励
我是darkchink,一个计算机相关从业者&一个摩托佬&AI狂热爱好者
本职工作是某互联网公司数据相关工作,欢迎来聊,内推或者交换信息
vx 二维码见: https://www.cnblogs.com/DarkChink/p/18598402

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

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

相关文章

leetcode:905. 按奇偶排序数组(python3解法)

难度&#xff1a;简单 给你一个整数数组 nums&#xff0c;将 nums 中的的所有偶数元素移动到数组的前面&#xff0c;后跟所有奇数元素。 返回满足此条件的 任一数组 作为答案。 示例 1&#xff1a; 输入&#xff1a;nums [3,1,2,4] 输出&#xff1a;[2,4,3,1] 解释&#xff1a…

断言与反射——以golang为例

断言 x.(T) 检查x的动态类型是否是T&#xff0c;其中x必须是接口值。 简单使用 func main() {var x interface{}x 100value1, ok : x.(int)if ok {fmt.Println(value1)}value2, ok : x.(string)if ok {//未打印fmt.Println(value2)} }需要注意如果不接受第二个参数就是OK,这…

【数据结构】排序算法(下篇·开端)·深剖数据难点

前引&#xff1a;前面我们通过层层学习&#xff0c;了解了Hoare大佬的排序精髓&#xff0c;今天我们学习的东西可能稍微有点难度&#xff0c;因此我们必须学会思想&#xff0c;我很受感慨&#xff0c;借此分享一下&#xff1a;【用1520分钟去调试】&#xff0c;如果我们遇到了任…

山东大学软件学院创新项目实训开发日志(9)之测试前后端连接

在正式开始前后端功能开发前&#xff0c;在队友的帮助下&#xff0c;成功完成了前后端测试连接&#xff1a; 首先在后端编写一个测试相应程序&#xff1a; 然后在前端创建vue 并且在index.js中添加一下元素&#xff1a; 然后进行测试&#xff0c;测试成功&#xff1a; 后续可…

蓝桥杯C++组算法知识点整理 · 考前突击(上)【小白适用】

【背景说明】本文的作者是一名算法竞赛小白&#xff0c;在第一次参加蓝桥杯之前希望整理一下自己会了哪些算法&#xff0c;于是有了本文的诞生。分享在这里也希望与众多学子共勉。如果时间允许的话&#xff0c;这一系列会分为上中下三部分和大家见面&#xff0c;祝大家竞赛顺利…

springboot调用python文件,python文件使用其他dat文件,适配windows和linux,以及docker环境的方案

介绍 后台是用springboot技术&#xff0c;其他同事做的算法是python&#xff0c;现在的需求是springboot调用python&#xff0c;python又需要调用其他的数据文件&#xff0c;比如dat文件&#xff0c;这个文件是app通过蓝牙获取智能戒指数据以后&#xff0c;保存到后台&#xf…

GSO-YOLO:基于全局稳定性优化的建筑工地目标检测算法解析

论文地址:https://arxiv.org/pdf/2407.00906 1. 论文概述 《GSO-YOLO: Global Stability Optimization YOLO for Construction Site Detection》提出了一种针对建筑工地复杂场景优化的目标检测模型。通过融合全局优化模块(GOM)​、稳定捕捉模块(SCM)​和创新的AIoU损失函…

系统思考—提升解决动态性复杂问题能力

感谢合作伙伴的信任推荐&#xff01; 客户今年的人才发展重点之一&#xff0c;是提升管理者应对动态性、复杂性问题的能力。 在深入交流后&#xff0c;系统思考作为关键能力模块&#xff0c;最终被纳入轮训项目——这不仅是一次培训合作&#xff0c;更是一场共同认知的跃迁&am…

P1162 洛谷 填涂颜色

题目描述 思考 看数据量 30 而且是个二维的&#xff0c;很像走迷宫 直接深搜&#xff01; 而且这个就是搜连通块 代码 一开始的15分代码&#xff0c;想的很简单&#xff0c;对dfs进行分类&#xff0c;如果是在边界上&#xff0c;就直接递归&#xff0c;不让其赋值&#xff0c…

docker安装nginx,基础命令,目录结构,配置文件结构

Nginx简介 Nginx是一款轻量级的Web服务器(动静分离)/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器。其特点是占有内存少&#xff0c;并发能力强. &#x1f517;官网 docker安装Nginx &#x1f433; 一、前提条件 • 已安装 Docker&#xff08;dock…

用Django和AJAX创建一个待办事项应用

用Django和AJAX创建一个待办事项应用 推荐超级课程: 本地离线DeepSeek AI方案部署实战教程【完全版】Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战目录 用Django和AJAX创建一个待办事项应用让我们创建一个简单的 Django 项目,其中包含不同类型的 A…

JavaScript:游戏开发的利器

在近年来的科技迅速发展中&#xff0c;JavaScript 已逐渐成为游戏开发领域中最受欢迎的编程语言之一。它的跨平台特性、广泛的社区支持、丰富的库和框架使得开发者能够快速、有效地创建各种类型的游戏。本文将深入探讨 JavaScript 在游戏开发中的优势。 一、跨平台支持 JavaSc…

C语言今天开始了学习

好多年没有弄了&#xff0c;还是捡起来弄下吧 用的vscode 建议大家参考这个配置 c语言vscode配置 c语言这个语言简单&#xff0c;但是今天听到了一个消息说python 不知道怎么debug。人才真多啊

电商素材革命:影刀RPA魔法指令3.0驱动批量去水印,实现秒级素材净化

本文 去除水印实操视频展示电商图片水印处理的困境​影刀 RPA 魔法指令 3.0 强势登场​利用魔法指令3.0两步实现去除水印操作关于影刀RPA 去除水印实操视频展示 我们这里选择了4张小红书里面比较帅气的图片&#xff0c;但凡用过小红书的都知道&#xff0c;小红书右下角是会有小…

CVA6:支持 Linux 的 RISC-V CPU CORE-V

RISC-V 是一种开源的可扩展指令集架构 (ISA)&#xff0c;在过去几年中广受欢迎。RISC-V 的主要特性之一是它采用整体架构中性设计&#xff0c;支持浮点运算、加载存储架构、符号扩展加速和多路复用器简化。这使得 RISC-V 成为 FPGA 上软处理器的经济实惠的选择。自 RISC-V ISA …

轻奢宅家|约克VRF带你畅享舒适居家体验

下班回到家最期待什么&#xff1f;当然是一阵阵沁人心脾的舒适感扑面而来啦&#xff01;      想要从头到脚都舒服自在&#xff1f;答案就在眼前——就是它&#xff01;约克VRF中央空调&#xff01;      约克VRF中央空调独特的臻静降噪技术&#xff0c;让空调运行音轻…

uniapp微信小程序图片生成水印

整体思路&#xff1a; 用户通过uni.chooseImage选择图片后&#xff0c;获得图片文件的path和size。通过path调用uni.getImageInfo获取图片信息&#xff0c;也就是图片宽高。图片宽高等比缩放至指定大小&#xff0c;不然手机处理起来非常久&#xff0c;因为手机随便拍拍就很大。…

不用额外下载jar包,idea快速查看使用的组件源码

以nacos为例子&#xff0c;在idea中引入了nacos依赖&#xff0c;就可以查看源码了。 2. idea选择open&#xff08;不关闭项目直接选择file-open也可以&#xff09;, 在maven的仓库里找到对应的包&#xff0c;打开 2.idea中选择 jar包&#xff0c;选择 add as library 3.这样j…

网络通讯协议UDP转发TCP工具_UdpToTcpRelay_双向版

UDP/TCP网络转发器程序说明书 1. 程序概述 本程序是一个高性能网络数据转发工具&#xff0c;支持UDP和TCP协议之间的双向数据转发&#xff0c;并具备以下核心功能&#xff1a; 协议转换&#xff1a;实现UDP↔TCP协议转换数据转换&#xff1a;支持十六进制/ASCII格式的数据转…

DIA——边缘检测

1.边缘 边缘是像素的突变位置。 2.常见边缘检测算法 通过找到一阶导数的极值点或者二阶导数的过零点来确定边缘像素的位置。边缘检测通常使用算子&#xff0c;即特定的卷积核。通过差分对离散的像素点求导&#xff0c;然后转化成卷积核进行卷积。使用卷积统一涵盖求导&…