以下流程为了方便理解均经过了简化。
首先,我们通过调用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::NewIterator。TableReader默认是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_iterations,DBIter就会直接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;