读操作的前期流程
对用户来说,读流程如下:
DB* db;
Options options;
std::string dbname = "/tmp/rocksdb4";
Status s = DB::Open(options, dbname, &db);
td::string myValue
db->Get(ReadOptions(), "a5", &myValue);
内部兜兜转转其实是走到了下面这个方法里(在db_impl.cc里)
Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
GetImplOptions& get_impl_options)
这里面大的流程就是先从mem读,如果没有就去imm读,如果还没有就去sst里面读。
处理从各层sst和缓存里面拿到kv的逻辑就在下面的代码里(实现在version_set.cc):
Version::Get(....)
Version::Get里面会按照L0的sst文件都检查一遍,然后之后的每层只用检查1个sst文件的逻辑进行搜索。
具体对某个sst文件的查询在Version::Get里面的TableCache::Get方法里。
对sst的查询在TableCache::Get里面!可以理解为每次查询某个sst的逻辑都包含了缓存。OK咱们这篇文章的重点就从这里开始。
TableCache::Get
我认为理解一个东西最好的逻辑,就是先假定它不存在,然后看看在没有它的情况整个流程如何运行,然后再去看看加上它都解决了那些问题。
如果咱们去掉所有的缓存逻辑,那么TableCache::Get里面就是两层
1 获取TableReader。
2 调用TableReader的Get方法。
1 获取TableReader
整体逻辑在TableCache::FindTable逻辑里面调用的TableCache::GetTableReader实现。
GetTableReader先通过PosixFileSystem构建了PosixMmapReadableFile,然后构建了RandomAccessFileReader,最终构建了TableReader。
这里有个问题TableReader是啥?它是什么作用?
慢慢说,我们知道一个sst的格式如下:
<beginning_of_file>
[data block 1]
[data block 2]
...
[data block N]
[meta block 1: filter block] (see section: "filter" Meta Block)
[meta block 2: stats block] (see section: "properties" Meta Block)
[meta block 3: compression dictionary block] (see section: "compression dictionary" Meta Block)
[meta block 4: range deletion block] (see section: "range deletion" Meta Block)
...
[meta block K: future extended block] (we may add more meta blocks in the future)
[metaindex block]
[index block]
[Footer] (fixed size; starts at file_size - sizeof(Footer))
<end_of_file>
读取一个sst的时候,就是先读取footer,然后根据footer定位到indeblock和metainde block
然后根据indexblock定位data block
根据meta indexblcok定位各个metablock (上面的结构代码里,包含了4种metablcok)
在BlockBasedTable::Open里面,就是按照如下的顺序解析各个block的。
// Read in the following order:
// 1. Footer
// 2. [metaindex block]
// 3. [meta block: properties]
// 4. [meta block: range deletion tombstone]
// 5. [meta block: compression dictionary]
// 6. [meta block: index]
// 7. [meta block: filter]
上面需要几次io?
举个例子,下面的流程就是解析index block的逻辑。
TableCache::GetTableReader
BlockBasedTableFactory::NewTableReader
BlockBasedTable::Open
BlockBasedTable::PrefetchIndexAndFilterBlocks
BlockBasedTable::CreateIndexReader
BinarySearchIndexReader::Create
BlockBasedTable::IndexReaderCommon::ReadIndexBlock
BlockBasedTable::RetrieveBlock
ReadBlockFromFile(define at block_based_table_reader.cc)
BlockFetcher::ReadBlockContents
BlockFetcher::GetBlockContents
换句话说,TableReader里面持有了一个sst里面的除了datablcok外的所有信息。
2 调用TableReader的Get方法
这里面的逻辑也简单,
1 先去布隆过滤器里面找。如果布隆过滤器说没有,那就肯定没有,直接返回;如果布隆过滤器说有,那就继续找。
2 从indexblock里面找到对应的datablcok,然后在datablcok上构建迭代器(这里需要读磁盘)
3 从datablock的迭代器里面,找到对应的kv。
4 检查kv是否已经删除,是否范围删除等等
缓存的作用
通过代码我们已经知道在TableCache::Get里面大的逻辑就是两个
1 获取TableReader(逻辑在TableCache::FindTable里)
2 调用TableReader的get方法
而TableCache::FindTable里面的逻辑就是如果从缓存里能拿到TableReader就直接返回,拿不到就按照前文的GetTableReader
参考资料
1 关于sst的格式
https://www.jianshu.com/p/d6ce3593a69e
https://www.cnblogs.com/lygin/p/17103522.html