leveldb源码分析——SSTable解析
2018-02-10 10:03
507 查看
上篇文章分析了sstable 构建,这里继续分析sstable解析。
table.cc文件 中Table类就代码sstable实例
看table 声明
发现只有一个Rep* rep_,struct Rep定义在table.cc中
与构建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用。
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 。
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的迭代器。
restart_offset_为重启点偏移量计算方法是 restart_offset_ = size_ - (1 + NumRestarts()) * sizeof(uint32_t), 计算方式请结合下图理解。
virtual void Seek(const Slice& target)查询k接口
在分析这个接口之前再看看block结构
index_block与data_block联系
这里的查找分两步
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。
current_ 为当前record偏移量,根据当前已知的key_,value_解析出下一个key_,value_,如果当前解析点到下一个重启点,调整当前重启点restart_index_下标。
NextEntryOffset()这个实现如下,注释很明了。
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_; }
相关文章推荐
- leveldb源码分析--SSTable之Compaction 详解
- levelDB源码分析-SSTable:Block
- levelDB源码分析-SSTable:.sst文件构建与读取
- leveldb源码分析--SSTable之block
- leveldb源码分析--SSTable之逻辑结构
- leveldb源码分析--SSTable之TableBuilder
- leveldb源码分析--SSTable之block
- leveldb源码分析——SSTable构建
- levelDB源码分析-SSTable
- leveldb源码分析--SSTable之Compaction
- MySQL系列:innodb源码分析之page结构解析
- log::Writer-levelDB源码解析
- 对Xabber源码解析的过程(1)工程目录分析
- webkit 源码分析系列--css样式解析
- Spring对注解(Annotation)处理源码分析2——解析和注入注解配置的资源
- Spring源码解析 - AbstractBeanFactory 实现接口与父类分析
- jquery源码分析二 21-94行源码解析
- Leveldb源码解析第二篇【Meta Block】
- opencv源码解析之(6):hog源码分析
- Mybatis源码分析 之 sql解析