RocksDB代码分析——读取流程

阅读量: searchstar Created: 2023-12-07 21:38:51 Updated: 2025-10-25 22:59:21
Categories: Tags:

以下流程为了方便理解均经过了简化。

首先,我们通过调用DB::Open来创建数据库,它返回了一个DB*DB::Open内部调用了DBImpl::Open,在里面构造了一个DBImpl*并转换成DB*返回。所以我们拿到的DB*其实是DBImpl*

Get

然后我们调用DB::Get来读取数据。DB::Get是个virtual函数,它被DBImpl::Get给override了。

DBImpl::Get里调用了DBImpl::GetImpl

DBImpl::GetImpl中,先调用DBImpl::GetAndRefSuperVersion拿到thread local的super version,然后调用MemTable::Get读取memory table和immutable memory table,如果找到了就返回,否则调用Version::Get继续在sv->current里查找。

Version::Get中,首先将key装进GetContext,然后遍历每个key range包含这个key的SSTable,把GetContext作为参数调用TableCache::Get来查找这个SSTable。如果都没找到就返回not found。

TableCache::Get中,调用TableReader::Get

Table默认是BlockBasedTable,所以这里的TableReader应该是BlockBasedTable的reader:

class BlockBasedTable : public TableReader {

BlockBasedTable::Get中,调用BlockBasedTable::NewIndexIterator获得block index的iterator,然后遍历key range包含目标key的data block。对每个遍历到的data block,调用BlockBasedTable::NewDataBlockIterator构造DataBlockIter biter,然后seek next在里面查找key。

BlockBasedTable::NewDataBlockIterator中,调用BlockBasedTable::RetrieveBlock,将块存入CachableEntry<Block> block

BlockBasedTable::RetrieveBlock中,参数use_cache=true,所以调用BlockBasedTable::MaybeReadBlockAndLoadToCache

BlockBasedTable::MaybeReadBlockAndLoadToCache中,参数contents=nullptr,所以调用BlockBasedTable::GetDataBlockFromCache,如果miss的话就调用BlockFetcher::ReadBlockContents

BlockFetcher::ReadBlockContents中,先尝试从persistent cache和prefetch buffer中读取。如果不成功的话,就调用RandomAccessFileReader::Read从文件中读取。

NewIterator

这里假设table是默认的BlockBasedTable。

DB::NewIterator实际是DBImpl::NewIterator。在其中调用DBImpl::NewIteratorImpl

DBImpl::NewIteratorImpl中,先调用NewArenaWrappedDbIterator构造一个ArenaWrappedDBIter* db_iter,然后调用DBImpl::NewInternalIterator,在db_iter管理的Arena里构造MergingIterator *internal_iter

DBImpl::NewInternalIterator中:

super_version->mem->NewIterator, 构建memory table的iterator
super_version->imm->AddIterators, 构建immutable memory table的iterator。
调用Version::AddIterators,对每层都调用Version::AddIteratorsForLevel
>L0的每个SSTable都调用TableCache::NewIterator。在里面调用TableReader::NewIteratorTableReader默认是BlockBasedTable,所以默认调用的是BlockBasedTable::NewIterator,创建一个BlockBasedTableIterator
>其他层每层构造一个LevelIterator
把memory table的,immutable memory table的,L0的每个SSTable的BlockBasedTableIterator,以及其他层的LevelIterator都合并成一个MergingIterator返回。

Seek

根据上文所述,DB::NewIterator返回的实际上是ArenaWrappedDBIter,所以Iterator::Seek调用的是ArenaWrappedDBIter::Seek。其中调用DBIter::Seek。其中调用MergingIterator::Seek

MergingIterator::Seek中,对每个子iterator调用Seek,比如memory table的iterator,L0中每个SSTable的BlockBasedTableIterator,以及其他层的LevelIterator。然后构建子iterator的小根堆,按照子iterator Seek之后的key排序。MergingIterator的key和value就是key最小的子iterator的key和value。

BlockBasedTableIterator::Seek调用BlockBasedTableIterator::SeekImpl

BlockBasedTableIterator::SeekImpl

如果有prefix bloom filter,就调用BlockBasedTableIterator::CheckPrefixMayMatch检查filter。如果filter返回negative就调用ResetDataIter把状态变成invalid然后返回。如果filter返回positive,就正常继续执行seek操作。
通过block index找到数据属于哪个block。如果找不到,就调用ResetDataIter把状态变成invalid然后返回。找到了data block的话,调用BlockBasedTableIterator::InitDataBlock,在里面构建block_iter_

Next

Iterator::Next调用的是ArenaWrappedDBIter::Next。其中调用DBIter::Next。其中调用MergingIterator::Next

MergingIterator::Next中,先给堆顶的子iterator调用Next。如果子iterator没有下一个了,就将其从堆里移出。否则调用BinaryHeap::replace_top来将这个原先在堆顶的子iterator往下挪,从而维护堆的性质。值得注意的是,LSM-tree通常越下面的sorted run越大,这就意味着当一个iterator在堆顶时,它倾向于保持在堆顶。可以证明,这种方式下,对于大于1的size ratio,每次Next里堆的维护成本均摊下来是常数。

对于同一个user key,MergingIterator会先返回sequence number大的,所以调用了MergingIterator::Next之后,下一个key可能是同一个user key,但是sequence number更小,也就是同一个user key的旧版本。而DBIter只需要返回一个最新版本即可。所以DBIter::Next接下来会调用DBIter::FindNextUserEntry,跳过那些可能的旧版本。值得注意的是,如果连续跳过的旧版本个数超过了max_sequential_skip_in_iterationsDBIter就会直接seek下一个user key(通过把seek key设置成那个user key并把sequence number置0)。

max_sequential_skip_in_iterations的注释:

  // An iteration->Next() sequentially skips over keys with the same
  // user-key unless this option is set. This number specifies the number
  // of keys (with the same userkey) that will be sequentially
  // skipped before a reseek is issued.
  //
  // Default: 8
  //
  // Dynamically changeable through SetOptions() API
  uint64_t max_sequential_skip_in_iterations = 8;