您的位置:首页 > 其它

leveldb源码分析——SSTable解析

2018-02-10 10:03 507 查看
上篇文章分析了sstable 构建,这里继续分析sstable解析。

table.cc文件 中Table类就代码sstable实例

看table 声明

class Table {
// 接口省略
private:
struct Rep;
Rep* rep_;
};


发现只有一个Rep* rep_,struct Rep定义在table.cc中

//用于解析sstable
struct Table::Rep {
~Rep() {
delete filter;
delete [] filter_data;
delete index_block;
}

Options options;//根据block_cache 以cache_id和offset为key,存储block
Status status;
RandomAccessFile* file;//sstable文件指针,用于随机读取sstable文件
uint64_t cache_id;//cache中sstable id
FilterBlockReader* filter;
const char* filter_data;

BlockHandle metaindex_handle;  // Handle to metaindex_block: saved from footer
Block* index_block; //指向data index block
};


与构建table 类似,table中也有一个私有嵌套类,记录table关键结构 index_block ,index_block是data_block的索引,table只保存index_block数据,并没保存data_block,因为根据index_block就能根据偏移量从sstable读取data_block,也是为了减少内存占用量。

解析sstable首先得需要打开sstable文件。

1 打开sstable文件

static Status Open(const Options& options, RandomAccessFile* file, uint64_t file_size, Table table); 用于创建一个table,table的Table::Rep记录了sstable一些信息,用于查询key-value用。

//打开sstable文件
Status Table::Open(const Options& options,
RandomAccessFile* file,
uint64_t size,
Table** table) {
*table = NULL;
if (size < Footer::kEncodedLength) {
return Status::Corruption("file is too short to be an sstable");
}

char footer_space[Footer::kEncodedLength];
Slice footer_input;
/*
* 1 读取footer,解析data_index_block 和meta_index_block
*/
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;

BlockContents contents;
Block* index_block = NULL;
if (s.ok()) {
ReadOptions opt;
if (options.paranoid_checks) {
opt.verify_checksums = true;
}
/*
* 2 读取 data_index_block 内容
*/
s = ReadBlock(file, opt, footer.index_handle(), &contents);
if (s.ok()) {
index_block = new Block(contents);
}
}

if (s.ok()) {
// We've successfully read the footer and the index block: we're
// ready to serve requests.
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 = NULL;
rep->filter = NULL;
*table = new Table(rep);
(*table)->ReadMeta(footer);
} else {
delete index_block;
}

return s;
}


1) Status s = file->Read(size - Footer::kEncodedLength, Footer::kEncodedLength,&footer_input, footer_space);

先读取sstable文件末尾 footer结构,因为这个结构记录index_block 和metaindex_block 偏移和大小。

2) s = ReadBlock(file, opt, footer.index_handle(), &contents);

读取index_block内容,这个接口的含义就是根据给的BlockHandle,读取block,这个接口简单就不贴代码了。

3) 读取block 内容后就创建一个Block index_block = new Block(contents);Block是一个重要对象,用于解析block。

4)(*table)->ReadMeta(footer); 就是用于读取filter_block。

2 从sstable 查询 key-value

分析sstable查询是理解sstable解析 的关键,查询sstable所用的接口是Table::InternalGet

Status Table::InternalGet(const ReadOptions& options, const Slice& k,
void* arg,
void (*saver)(void*, const Slice&, const Slice&)) {
Status s;
/*
* 1 根据data_index_block找到data_block
*/
Iterator* iiter = rep_->index_block->NewIterator(rep_->options.comparator);
iiter->Seek(k);//返回的value是blockhandler 记录data_block大小和偏移
if (iiter->Valid()) {
Slice handle_value = iiter->value();
FilterBlockReader* filter = rep_->filter;
BlockHandle handle;
//优先通过filter查找 data_block中key-value
if (filter != NULL &&
handle.DecodeFrom(&handle_value).ok() &&
!filter->KeyMayMatch(handle.offset(), k)) {//通过filter查找,如果返回为false ,就一定不存在,如果为真,可能存在,所以就通过data_block查找,能提高不存在的key查找效率
// Not found
} else {
/*
* 2 从data_block 查找key value
*/
Iterator* block_iter = BlockReader(this, options, iiter->value());
block_iter->Seek(k);
if (block_iter->Valid()) {
(*saver)(arg, block_iter->key(), block_iter->value());
}
s = block_iter->status();
delete block_iter;
}
}
if (s.ok()) {
s = iiter->status();
}
delete iiter;
return s;
}


sstable key-value 查询分两步

1 根据index_block查询key-value 所在data_block

2 从data_block查询key-value。

index_block 和data_block都是block结构,而block是由一系列record 按照key从小到大组成,所以key-value查询的关键是从有序的record记录中查找目标record,key-value的查询是通过 block内部迭代器完成,因此这两步实现查询的思路是一样的。

Iterator* iiter = rep_->index_block->NewIterator(rep_->options.comparator);创建一个用于遍历block内部record的迭代器。

Iterator* Block::NewIterator(const Comparator* cmp) {
if (size_ < sizeof(uint32_t)) {
return NewErrorIterator(Status::Corruption("bad block contents"));
}
const uint32_t num_restarts = NumRestarts();//获取重启点数量
if (num_restarts == 0) {
return NewEmptyIterator();
} else {
return new Iter(cmp, data_, restart_offset_, num_restarts);
}
}


restart_offset_为重启点偏移量计算方法是 restart_offset_ = size_ - (1 + NumRestarts()) * sizeof(uint32_t), 计算方式请结合下图理解。

virtual void Seek(const Slice& target)查询k接口

在分析这个接口之前再看看block结构



index_block与data_block联系



//在data/index block内查找 target
virtual void Seek(const Slice& target) {
// 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;
/* 1 找重启点  找到所有重启点中最后一个重启点首元素小于target的重启点,target一定在这个之后
* 因为元素依次递增
*/
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);
//指向重启点首元素,所以shared==0
if (key_ptr == NULL || (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;
}
}

// Linear search (within restart block) for first key >= target
/*
*  2 从block_restart_interval内寻找第一个大于或者等于target的key
*/
SeekToRestartPoint(left);
while (true) {
if (!ParseNextKey()) {
return;
}

if (Compare(key_, target) >= 0) {
return;
}
}
}


这里的查找分两步

1 查找target 所在group 2 遍历group 查找target

通过 index_block查找data_block: 就是查找key-value所在data_block,因为index_block每个record的记录 data_block 最大key 和 offset size,如果data_block 的largest_key都比target小,那么target一定不属于这个data_bock,但是这里使用的二分查找,如果largest_key1<=target<=largest_key2,那么可以确定target 一定在 largest_key1之后,因此以largest_key1作为起点(即left)循环遍历 找到第一个 record largest_key>=target 那个target 一定属于这个record 记录的data_block ,这个就是ParseNextKey这个接口的思想。

对于data_block内查找与index_block查找思想一样的,因为都是block结构。

bool ParseNextKey()

根据当前record获取下一个record。

//从block_restart_interval内解析 根据上一个key-value解析下一个key-value
bool ParseNextKey() {
current_ = NextEntryOffset();//当前解析的偏移量
const char* p = data_ + current_;//当前解析record(起始点)
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;
p = DecodeEntry(p, limit, &shared, &non_shared, &value_length);
if (p == NULL || 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_;//根据当前解析的record位置(current_),调整restart_index_
}
return true;
}
}


current_ 为当前record偏移量,根据当前已知的key_,value_解析出下一个key_,value_,如果当前解析点到下一个重启点,调整当前重启点restart_index_下标。

NextEntryOffset()这个实现如下,注释很明了。

// Return the offset in data_ just past the end of the current entry.
//  record1 shared key length | not shared key length| value length |key1 data |value1 data
//  record2 shared key length | not shared key length| value length |key2 data |value2 data
// value_ 为value1 data,所以返回的是record2 的offset
inline uint32_t NextEntryOffset() const {
return (value_.data() + value_.size()) - data_;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: