leveldb 源码分析 9¶
本系列《leveldb 源码分析》共有 22 篇文章,这是第九篇
6 SSTable 之 3¶
6.5 读取 sstable 文件¶
6.5.1 类层次¶
Sstable 文件的读取逻辑在 类 Table 中,其中涉及到的类还是比较多的,如图 6.5-1 所示。
Table 类导出的函数只有 3 个,先从这三个导出函数开始分析。其中涉及到的类(包括上图中为画出的)都会一一遇到,然后再一一拆解。
本节分析 sstable 的打开逻辑,后面再分析 key 的查找与数据遍历。
6.5.2 Table::Open()¶
打开一个 sstable 文件,函数声明为:
static Status Open(const Options& options, RandomAccessFile* file,
uint64_tfile_size, Table** table);
这是 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;
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);
}
}
这是通过调用 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;
}
到这里,Table 的打开操作就已经为完成了。下面来分析上面用到的 ReadBlock() 和 ReadMeta() 函数。
6.5.3 ReadBlock()¶
前面讲过 block 的格式,以及 Block 的写入(TableBuilder::WriteRawBlock),现在我们可以轻松的分析 Block 的读取操作 了。
这是一个 全局函数,声明为:
Status ReadBlock(RandomAccessFile* file, const ReadOptions& options,
const BlockHandle&handle, BlockContents* result);
下面来分析实现逻辑:
2¶
S1 初始化结果 result,BlockContents 是一个有 3 个成员的结构体。¶
result->data = Slice();
result->cachable = false; // 无cache
result->heap_allocated =false; // 非heap分配
S2 根据 handle 指定的偏移和大小,读取 block 内容,type 和 crc32 值,其中常量 kBlockTrailerSize=5= 1byte 的 type 和 4bytes 的 crc32。¶
Status s = file->Read(handle.offset(),handle.size() + kBlockTrailerSize,
&contents, buf);
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;
}
对于压缩存储,解压后的字符串存储需要读取者自行分配的,所以标记都是 true。
6.5.4 Table::ReadMeta()¶
解决完了 Block 的读取,接下来就是 meta 的读 取了。函数声明为:
void Table::ReadMeta(const Footer& footer)
函数逻辑并不复杂 。
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信息也没关系
}
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;
6.5.5 Table::ReadFilter()¶
根据指定的偏移和大小,读取 filter,函数声明:
void ReadFilter(const Slice& filter_handle_value);
简单分析下函数逻辑:
4¶
S1 从传入的 filter_handle_value Decode 出 BlockHandle,这是 filter 的偏移和大小;¶
BlockHandle filter_handle;
filter_handle.DecodeFrom(&filter_handle_value);
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);
以上就是 sstable 文件的读取操作,不算复杂。