LevelDB 支持的读操作分为两种:
点查询(Point Query):读一个 key 的数据。 范围查询(Range Query):有序读一段 key 范围的数据。
本文主要介绍点查询的实现。
Get 接口
LevelDB 通过 leveldb::DB::Get[1] 接口对外提供点查询的能力,具体的实现是 leveldb::DBImpl::Get[2]。接口声明如下:
virtual Status Get(const ReadOptions& options, const Slice& key, std::string* value) = 0;
key 和 value 都很简单:key 是本次查询的目标;如果找到对应的 key,数据会保存到 *value 中。
leveldb::ReadOptions[3] 是读操作的控制参数:
verify_checksums - 是否检查 crc32 校验和,默认 false。 fill_cache - 从 SSTable 读取的数据是否要放到 block cache,默认 true。 snapshot - 表示本次读操作要从哪个快照读取。默认是 nullptr,会从当前最新的快照读取数据。
快照
先来看看 leveldb::Snapshot[4] 的实现。leveldb::Snapshot 是个空壳,具体实现是在 leveldb::SnapshotImpl[5] ,也相当简单,主要就维护一个 sequence_number_—— 只读取小于等于 sequence_number_ 的数据。
如果一个快照有被外部使用(GetSnapshot[6]),这个快照对应的 SnapshotImpl 对象会被放在 SnapshotList[7] 中。Compaction 的时候,遇到可以清理的数据,还需要判断是否会影响这些快照[8]。
Get 的实现
我们来看看 leveldb::DBImpl::Get 的实现:
获取互斥锁[9]。 获取本次读操作的 Sequence Number[10]:如果 ReadOptions 参数的 snapshot 不为空,则使用这个 snapshot 的 sequence number;否则,默认使用 LastSequence(LastSequence 会在每次写操作后更新)。 MemTable, Immutable Memtable 和 Current Version(SSTable) 增加引用计数[11],避免在读取过程中被后台线程进行 compaction 时“垃圾回收”了。 释放互斥锁[12] 。所以,下面 5 和 6 两步是没有持有锁的,不同线程的请求可以并发执行。 构造 LookupKey[13] 。 执行查找: 从 MemTable 查找[14] 。 从 Immutable Memtable 查找[15]。 从 SSTable 查找[16]。 获取互斥锁[17] 更新 SSTable 的读统计信息[18],根据统计结果决定是否调度后台 Compaction[19]。=> 极少遇到有读触发 compaction 的场景,这一步的似乎意义不大。 MemTable, Immutable Memtable 和 Current Version 减少引用计数,返回结果[20]。
主要逻辑在于读 MemTable(包括 Immutable MemTable) 和读 SSTable。
LookupKey
LevelDB 通过 user_key 和 sequence 构造 leveldb::LookupKey[21] ,用于 LevelDB 内部接口的查找。其格式为:
klength 的类型是 varint32,存储 userkey + tag 的长度[22]。 userkey 就是 Get 接口传入的 key 参数。 tag 是 7 字节 sequence number 和 1 字节 value type[23]。 一个 varint32 最多需要 5 个字节,tag 需要 8 字节。所以一个 LookupKey 最多需要分配 usize + 13 字节[24]内存(usize 是 userkey 的 size)。
读 MemTable
MemTable 底层的实现是 leveldb::SkipList[25] ,具体可以参考本系列的一篇文章《LevelDB 完全解析(1):MemTable》。
leveldb::SkipList 支持无锁一写多读,具体来讲:
写写并发由上层进行同步,保证同一时刻最多只有一个线程会写 SkipList。 读写并发不需要外部同步,只要保证 SkipList 不会被垃圾回收就好——这个通过引用计数保证。
具体实现查询操作的是 FindGreaterOrEqual[26] 函数,返回值是一个指针,指向第一个大于等于 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 会进行检查[27],再返回最终结果。
读 SSTTable
LevelDB 中将 SSTable 的管理实现成 leveldb::Version[28] ,同时通过 leveldb::VersionSet[29] 实现多版本管理。
Version::Get
SSTable 的点查询是从 leveldb::Version::Get[30] 开始的:
根据每个 SSTable 的 key 范围 [smallest, largest] 收集可能需要查找的 SSTable,按照从新到旧维护
level-0[31] 的每个 SSTable 的 key 范围可能相交,每一个 SSTable 都需要判断。 非 level-0 的每一层内[32],SSTable 的 key 范围是不相交的——SSTable 是根据 key 范围有序排序的,可以通过二分查找优化查找效率。 根据上一步收集到的 SSTable,按照从新到旧开始查找。如果找到了,就可以直接返回结果。每个 SSTable 的具体查找逻辑是在 leveldb::TableCache::Get[33]。
TableCache::Get
FindTable[34] - 从 table cache 中找到对应的 table 对象。FindTable[35] 实现了缓存 table 对象的逻辑。 Table::InternalGet[36] - 执行查找。
Table::InternalGet
Table::InternalGet[37] 实现了从一个 SSTable 中查找一个 key 的逻辑。
从 index block 查找对应的 data block[38]。如果没有对应的 data block,说明 key 不存在,返回。 通过 bloom filter 判断 key 是否存在[39]。 读取 data block 进行查找[40]。 Table::BlockReader[41] 封装了 block cache 和 SSTable 读取 data block 的逻辑。 Data block 中的数据查找逻辑与其格式强相关,可以参考本系列的一篇文章《LevelDB 完全解析(3):SSTable》。
小结

最后,用这张简图来总结一下 LevelDB Get 操作的逻辑:这是一个从上到下的过程,这个过程可能产生多次 I/O。
文中“蓝色字体”部分均有跳转,大部分是引用了 Github 上的源码链接,可以点击【阅读原文】查看。
参考资料
leveldb::DB::Get: https://github.com/google/leveldb/blob/1.22/include/leveldb/db.h#L80-L88
[2]leveldb::DBImpl::Get: https://github.com/google/leveldb/blob/1.22/db/db_impl.cc#L1100-L1145
[3]leveldb::ReadOptions: https://github.com/google/leveldb/blob/1.22/include/leveldb/options.h#L146-L162
[4]leveldb::Snapshot: https://github.com/google/leveldb/blob/1.22/include/leveldb/db.h#L26-L32
[5]leveldb::SnapshotImpl: https://github.com/google/leveldb/blob/1.22/db/snapshot.h#L17-L37
[6]GetSnapshot: https://github.com/google/leveldb/blob/1.22/db/db_impl.cc#L1166-L1169
[7]SnapshotList: https://github.com/google/leveldb/blob/1.22/db/snapshot.h#L39-L91
[8]是否会影响这些快照: https://github.com/google/leveldb/blob/1.22/db/db_impl.cc#L74-L78
[9]获取互斥锁: https://github.com/google/leveldb/blob/1.22/db/db_impl.cc#L1103
[10]获取本次读操作的 Sequence Number: https://github.com/google/leveldb/blob/1.22/db/db_impl.cc#L1104-L1110
[11]增加引用计数: https://github.com/google/leveldb/blob/1.22/db/db_impl.cc#L1112-L1117
[12]释放互斥锁: https://github.com/google/leveldb/blob/1.22/db/db_impl.cc#L1124
[13]构造 LookupKey: https://github.com/google/leveldb/blob/1.22/db/db_impl.cc#L1126
[14]从 MemTable 查找: https://github.com/google/leveldb/blob/1.22/db/db_impl.cc#L1127
[15]从 Immutable Memtable 查找: https://github.com/google/leveldb/blob/1.22/db/db_impl.cc#L1129
[16]从 SSTable 查找: https://github.com/google/leveldb/blob/1.22/db/db_impl.cc#L1132
[17]获取互斥锁: https://github.com/google/leveldb/blob/1.22/db/db_impl.cc#L1135
[18]更新 SSTable 的读统计信息: https://github.com/google/leveldb/blob/1.22/db/db_impl.cc#L1138
[19]调度后台 Compaction: https://github.com/google/leveldb/blob/1.22/db/db_impl.cc#L1139
[20]减少引用计数,返回结果: https://github.com/google/leveldb/blob/1.22/db/db_impl.cc#L1141-L1144
[21]leveldb::LookupKey: https://github.com/google/leveldb/blob/1.22/db/dbformat.h#L178-L210
[22]存储 userkey + tag 的长度: https://github.com/google/leveldb/blob/1.22/db/dbformat.cc#L131
[23]value type: https://github.com/google/leveldb/blob/1.22/db/dbformat.h#L49-L59
[24]usize + 13 字节: https://github.com/google/leveldb/blob/1.22/db/dbformat.cc#L123
[25]leveldb::SkipList: https://github.com/google/leveldb/blob/1.22/db/skiplist.h#L42
[26]FindGreaterOrEqual: https://github.com/google/leveldb/blob/1.22/db/skiplist.h#L260-L281
[27]对 FindGreaterOrEqual 返回的 key 会进行检查: https://github.com/google/leveldb/blob/1.22/db/memtable.cc#L111-L118
[28]leveldb::Version: https://github.com/google/leveldb/blob/1.22/db/version_set.h#L60
[29]leveldb::VersionSet: https://github.com/google/leveldb/blob/1.22/db/version_set.h#L167
[30]leveldb::Version::Get: https://github.com/google/leveldb/blob/1.22/db/version_set.cc#L326
[31]level-0: https://github.com/google/leveldb/blob/1.22/db/version_set.cc#L349-L365
[32]非 level-0 的每一层内: https://github.com/google/leveldb/blob/1.22/db/version_set.cc#L365-L381
[33]leveldb::TableCache::Get: https://github.com/google/leveldb/blob/1.22/db/table_cache.cc#L100
[34]FindTable: https://github.com/google/leveldb/blob/1.22/db/table_cache.cc#L105
[35]FindTable: https://github.com/google/leveldb/blob/1.22/db/table_cache.cc#L41-L76
[36]Table::InternalGet: https://github.com/google/leveldb/blob/1.22/db/table_cache.cc#L108
[37]Table::InternalGet: https://github.com/google/leveldb/blob/1.22/table/table.cc#L216-L244
[38]从 index block 查找对应的 data block: https://github.com/google/leveldb/blob/1.22/table/table.cc#L220-L221
[39]通过 bloom filter 判断 key 是否存在: https://github.com/google/leveldb/blob/1.22/table/table.cc#L226-L227
[40]读取 data block 进行查找: https://github.com/google/leveldb/blob/1.22/table/table.cc#L229-L237
[41]Table::BlockReader: https://github.com/google/leveldb/blob/1.22/table/table.cc#L155-L208




