CppGuide社区 CppGuide社区
首页
  • 最新谷歌C++风格指南(含C++17/20)
  • C++17详解
  • C++20完全指南
  • C++23快速入门
  • C++语言面试问题集锦
  • 🔥C/C++后端开发常见面试题解析 (opens new window)
  • 网络编程面试题 (opens new window)
  • 网络编程面试题 答案详解 (opens new window)
  • 聊聊WebServer作面试项目那些事儿 (opens new window)
  • 字节跳动面试官现身说 (opens new window)
  • 技术简历指南 (opens new window)
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 从零用C语言写一个Redis
  • Windows 10系统编程
  • Go语言特性

    • Go开发实用指南
    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
  • Rust编程

    • Rust编程指南
  • 数据库

    • SQL零基础指南
    • MySQL开发与调试指南
  • Linux内核

    • 心中的内核 —— 在阅读内核代码之前先理解内核
    • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
    • TCP源码实现超详细注释版.pdf (opens new window)
GitHub (opens new window)
首页
  • 最新谷歌C++风格指南(含C++17/20)
  • C++17详解
  • C++20完全指南
  • C++23快速入门
  • C++语言面试问题集锦
  • 🔥C/C++后端开发常见面试题解析 (opens new window)
  • 网络编程面试题 (opens new window)
  • 网络编程面试题 答案详解 (opens new window)
  • 聊聊WebServer作面试项目那些事儿 (opens new window)
  • 字节跳动面试官现身说 (opens new window)
  • 技术简历指南 (opens new window)
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 从零用C语言写一个Redis
  • Windows 10系统编程
  • Go语言特性

    • Go开发实用指南
    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
  • Rust编程

    • Rust编程指南
  • 数据库

    • SQL零基础指南
    • MySQL开发与调试指南
  • Linux内核

    • 心中的内核 —— 在阅读内核代码之前先理解内核
    • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
    • TCP源码实现超详细注释版.pdf (opens new window)
GitHub (opens new window)
  • leveldb源码分析1
  • leveldb源码分析2
  • leveldb源码分析3
  • leveldb源码分析4
  • leveldb源码分析5
  • leveldb源码分析6
  • leveldb源码分析7
  • leveldb源码分析8
  • leveldb源码分析9
    • 6 SSTable之3
      • 6.5 读取sstable文件
      • 6.5.1 类层次
      • 6.5.2 Table::Open()
      • *1*
      • S1 首先从文件的结尾读取Footer,并Decode到Footer对象中,如果文件长度小于Footer的长度,则报错。Footer的decode很简单,就是根据前面的Footer结构,解析并判断magic number是否正确,解析出meta index和index block的偏移和长度。
      • S2 解析出了Footer,我们就可以读取index block和meta index了,首先读取index block。
      • S3 已经成功读取了footer和index block,此时table已经可以响应请求了。构建table对象,并读取metaindex数据构建filter policy。如果option打开了cache,还要为table创建cache。
      • 6.5.3 ReadBlock()
      • 2
      • S1 初始化结果result,BlockContents是一个有3个成员的结构体。
      • S2 根据handle指定的偏移和大小,读取block内容,type和crc32值,其中常量kBlockTrailerSize=5= 1byte的type和4bytes的crc32。
      • S3 如果option要校验CRC32,则计算content + type的CRC32并校验。
      • S4 最后根据type指定的存储类型,如果是非压缩的,则直接取数据赋给result,否则先解压,把解压结果赋给result,目前支持的是snappy压缩。
      • 6.5.4 Table::ReadMeta()
      • 3
      • S1首先调用ReadBlock读取meta的内容
      • S2 根据读取的content构建Block,找到指定的filter;如果找到了就调用ReadFilter构建filter对象。Block的分析留在后面。
      • 6.5.5 Table::ReadFilter()
      • 4
      • S1 从传入的filterhandlevalue Decode出BlockHandle,这是filter的偏移和大小;
      • S2 根据解析出的位置读取filter内容,ReadBlock。如果block的heapallocated为true,表明需要自行释放内存,因此要把指针保存在filterdata中。最后根据读取的data创建FilterBlockReader对象。
  • leveldb源码分析10
  • leveldb源码分析11
  • leveldb源码分析12
  • leveldb源码分析13
  • leveldb源码分析14
  • leveldb源码分析15
  • leveldb源码分析16
  • leveldb源码分析17
  • leveldb源码分析18
  • leveldb源码分析19
  • leveldb源码分析20
  • leveldb源码分析21
  • leveldb源码分析22
  • leveldb源码分析
zhangxf
2023-04-02
目录

leveldb源码分析9

# leveldb源码分析9

本系列《leveldb源码分析》共有22篇文章,这是第九篇

# 6 SSTable之3

# 6.5 读取sstable文件

# 6.5.1 类层次

Sstable文件的读取逻辑在类Table中,其中涉及到的类还是比较多的,如图6.5-1所示。

img

Table类导出的函数只有3个,先从这三个导出函数开始分析。其中涉及到的类(包括上图中为画出的)都会一一遇到,然后再一一拆解。

本节分析sstable的打开逻辑,后面再分析key的查找与数据遍历。

# 6.5.2 Table::Open()

打开一个sstable文件,函数声明为:

static Status Open(const Options& options, RandomAccessFile* file,
                   uint64_tfile_size, Table** table);
1
2

这是Table类的一个静态函数,如果操作成功,指针*table指向新打开的表,否则返回错误。

要打开的文件和大小分别由参数file和file_size指定;option是一些选项;

下面就分析下函数逻辑:

# *1*
# S1 首先从文件的结尾读取Footer,并Decode到Footer对象中,如果文件长度小于Footer的长度,则报错。Footer的decode很简单,就是根据前面的Footer结构,解析并判断magic number是否正确,解析出meta index和index block的偏移和长度。
*table = NULL;
if (size <Footer::kEncodedLength) 
{ 
     // 文件太短
     returnStatus::InvalidArgument("file is too short to be an sstable");
}
charfooter_space[Footer::kEncodedLength]; // Footer大小是固定的
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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# S2 解析出了Footer,我们就可以读取index block和meta index了,首先读取index block。
BlockContents contents;
Block* index_block = NULL;
if (s.ok()) 
{
     s = ReadBlock(file, ReadOptions(),footer.index_handle(), &contents);
     if (s.ok())
     {
          index_block = newBlock(contents);
     }
}
1
2
3
4
5
6
7
8
9
10

这是通过调用ReadBlock完成的,下面会分析这个函数。

# S3 已经成功读取了footer和index block,此时table已经可以响应请求了。构建table对象,并读取metaindex数据构建filter policy。如果option打开了cache,还要为table创建cache。
if (s.ok())
{
     // 已成功读取footer和index block: 可以响应请求了
     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 = rep->filter= NULL;
     *table = new Table(rep);
     (*table)->ReadMeta(footer);
     // 调用ReadMeta读取metaindex
} 
else 
{
     if (index_block) deleteindex_block;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

到这里,Table的打开操作就已经为完成了。下面来分析上面用到的ReadBlock()和ReadMeta()函数。

# 6.5.3 ReadBlock()

前面讲过block的格式,以及Block的写入(TableBuilder::WriteRawBlock),现在我们可以轻松的分析Block的读取操作了。

这是一个全局函数,声明为:

Status ReadBlock(RandomAccessFile* file, const ReadOptions& options, 
                 const BlockHandle&handle, BlockContents* result);
1
2

下面来分析实现逻辑:

# 2
# S1 初始化结果result,BlockContents是一个有3个成员的结构体。
result->data = Slice();
result->cachable = false;      // 无cache
result->heap_allocated =false; // 非heap分配
1
2
3
# S2 根据handle指定的偏移和大小,读取block内容,type和crc32值,其中常量kBlockTrailerSize=5= 1byte的type和4bytes的crc32。
Status s = file->Read(handle.offset(),handle.size() + kBlockTrailerSize,
                      &contents, buf);
1
2
# S3 如果option要校验CRC32,则计算content + type的CRC32并校验。
# S4 最后根据type指定的存储类型,如果是非压缩的,则直接取数据赋给result,否则先解压,把解压结果赋给result,目前支持的是snappy压缩。

另外,文件的Read接口返回的Slice结果,其data指针可能没有使用我们传入的buf,如果没有,那么释放Slice的data指针就是我们的事情,否则就是文件来管理的。

if (data != buf) 
{ 
     // 文件自己管理,cacheable等标记设置为false
     delete[] buf;
     result->data =Slice(data, n);
     result->heap_allocated= result->cachable =false;
} 
else 
{ 
      // 读取者自己管理,标记设置为true
      result->data =Slice(buf, n);
      result->heap_allocated= result->cachable = true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

对于压缩存储,解压后的字符串存储需要读取者自行分配的,所以标记都是true。

# 6.5.4 Table::ReadMeta()

解决完了Block的读取,接下来就是meta的读取了。函数声明为:

void Table::ReadMeta(const Footer& footer)
1

函数逻辑并不复杂 。

# 3
# S1首先调用ReadBlock读取meta的内容
if(rep_->options.filter_policy == NULL) return; 
// 不需要metadata
ReadOptions opt;
BlockContents contents;
if (!ReadBlock(rep_->file,opt, footer.metaindex_handle(), &contents).ok()) 
{
      return;  // 失败了也没报错,因为没有meta信息也没关系
}
1
2
3
4
5
6
7
8
# S2 根据读取的content构建Block,找到指定的filter;如果找到了就调用ReadFilter构建filter对象。Block的分析留在后面。
Block* meta = newBlock(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;
1
2
3
4
5
6
7
8

# 6.5.5 Table::ReadFilter()

根据指定的偏移和大小,读取filter,函数声明:

void ReadFilter(const Slice& filter_handle_value);
1

简单分析下函数逻辑:

# 4
# S1 从传入的filter_handle_value Decode出BlockHandle,这是filter的偏移和大小;
BlockHandle filter_handle;
filter_handle.DecodeFrom(&filter_handle_value);
1
2
# S2 根据解析出的位置读取filter内容,ReadBlock。如果block的heap_allocated为true,表明需要自行释放内存,因此要把指针保存在filter_data中。最后根据读取的data创建FilterBlockReader对象。
ReadOptions opt;
BlockContents block;
ReadBlock(rep_->file, opt,filter_handle, &block);
if (block.heap_allocated)rep_->filter_data = block.data.data(); 
// 需要自行释放内存
rep_->filter = newFilterBlockReader(rep_->options.filter_policy, block.data);
1
2
3
4
5
6

以上就是sstable文件的读取操作,不算复杂。

编辑 (opens new window)
上次更新: 2023/12/11, 22:32:09
leveldb源码分析8
leveldb源码分析10

← leveldb源码分析8 leveldb源码分析10→

最近更新
01
第二章 关键字static及其不同用法
03-27
02
第一章 auto与类型推导
03-27
03
第四章 Lambda函数
03-27
更多文章>
Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式