LevelDB 支持的读操作分为两种:
本文主要介绍点查询的实现。
LevelDB 通过 leveldb::DB::Get 接口对外提供点查询的能力,具体的实现是 leveldb::DBImpl::Get。接口声明如下:
virtual Status Get(const ReadOptions& options, const Slice& key, std::string* value) = 0;
key 和 value 都很简单:key 是本次查询的目标;如果找到对应的 key,数据会保存到 *value 中。
leveldb::ReadOptions 是读操作的控制参数:
先来看看 leveldb::Snapshot 的实现。leveldb::Snapshot 是个空壳,具体实现是在 leveldb::SnapshotImpl ,也相当简单,主要就维护一个 sequence_number_—— 只读取小于等于 sequence_number_ 的数据。
如果一个快照有被外部使用(GetSnapshot),这个快照对应的 SnapshotImpl 对象会被放在 SnapshotList 中。Compaction 的时候,遇到可以清理的数据,还需要判断是否会影响这些快照。
我们来看看 leveldb::DBImpl::Get 的实现:
主要逻辑在于读 MemTable(包括 Immutable MemTable) 和读 SSTable。
LevelDB 通过 user_key 和 sequence 构造 leveldb::LookupKey ,用于 LevelDB 内部接口的查找。其格式为:
MemTable 底层的实现是 leveldb::SkipList ,具体可以参考本系列的一篇文章《LevelDB 完全解析(1):MemTable》。
leveldb::SkipList 支持无锁一写多读,具体来讲:
具体实现查询操作的是 FindGreaterOrEqual 函数,返回值是一个指针,指向第一个大于等于 key 的结点(如果不存大于等于 key 的结点,则是 nullptr)。
在 MemTable 中,同一个 userkey 的多个版本是按照 sequence number 降序排序的,也就说“sequence number 小的在排序中比较大,sequence number 大的在排序中比较小”。所以,如果使用一个旧的 snapshot,只能查到比这个 snapshot 旧(或一样旧)的数据。
举个例子,假设 userkey 是 hello,当前有 3 个版本—— sequence number 分别是 5、10、20。那么它们在 MemTable 中的排序(从小到大)是:
... | hello-20 | hello-10 | hello-5 | ...
假设我们通过一个 sequence number 为 15 的快照进行查找,拼装 LookupKey 为 hello-15,查找发现第一个大于等于 hello-15 的 key 是 hello-10。
如果通过一个 sequence number 大于等于 20 的快照对 hello 这个 key 进行查找,那么 FindGreaterOrEqual 返回的就是执行 hello-20 的指针。
如果我们通过一个 sequence number 为 1 的快照进行查找呢?hello 的几个版本都不符合要求,FindGreaterOrEuqal 返回的不是 hello 这个 user key 的数据。所以,MemTable 对 FindGreaterOrEqual 返回的 key 会进行检查,再返回最终结果。
LevelDB 中将 SSTable 的管理实现成 leveldb::Version ,同时通过 leveldb::VersionSet 实现多版本管理。
SSTable 的点查询是从 leveldb::Version::Get 开始的:
1. [level-0](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Fgoogle%2Fleveldb%2Fblob%2F1.22%2Fdb%2Fversion_set.cc%23L349-L365) 的每个 SSTable 的 key 范围可能相交,每一个 SSTable 都需要判断。
2. [非 level-0 的每一层内](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Fgoogle%2Fleveldb%2Fblob%2F1.22%2Fdb%2Fversion_set.cc%23L365-L381),SSTable 的 key 范围是不相交的——SSTable 是根据 key 范围有序排序的,可以通过二分查找优化查找效率。根据上一步收集到的 SSTable,按照从新到旧开始查找。如果找到了,就可以直接返回结果。每个 SSTable 的具体查找逻辑是在
Table::InternalGet 实现了从一个 SSTable 中查找一个 key 的逻辑。
最后,用这张简图来总结一下 LevelDB Get 操作的逻辑:这是一个从上到下的过程,这个过程可能产生多次 I/O。