leveldb 源码分析(五) – Read

有了之前的铺垫,leveldb 的读操作就很显然了。按照数据的新旧依次读:

  1. memtable
  2. immutable memtable
  3. level-0 中按照 file number 倒序
  4. level1 -> level-6

因为 leveldb 保证了更新的数据一定会比旧的数据先找到,所以当找到第一个 key 相同且 sequence 小于等于 target key 的就停止。

Cache

leveldb 中使用 LRU cache 来提高读的性能,compaction 时也会从 cache 中获取数据,有两种 cache

  • TableCache:缓存打开的 sstable,容量为 max_open_files(1000)
  • BlockCache:缓存读取的 Data Block,默认容量为 8MB

leveldb 中优先使用 mmap 将整个 sstable 映射进内存,失败时才会打开为文件读取,两种实现分别继承 RandomAccessFile 实现多态。 在创建 Table 时会读取 index blockfilter blockdata block 会按需读取。当 sstablemmap 进来的时,BlockCache 就没用了,因为所有 数据都已缓存在 TableCache 中,只有文件类型的 Table 才会使用 BlockCache

LRU Cache

leveldb 中实现的是并发安全的 LRU cache。实现也很简单:

  • listhashtable 实现的 LRUCache,由 mutex 保护,保证并发安全。
  • 为了减少锁的竞争,在 LRUCache 基础之上实现了 ShardedLRUCache,共有 16LRUCache,使用时会 shard 到指定的 LRUCache

因为 LRUCache 会有多个线程使用,需要做一些特殊处理:

  • 引用计数:使用引用计数保证数据使用时的有效性,因为有可能被淘汰的数据仍有线程在使用。
  • 使用 2 个链表保存数据:
    • lru_:保存存放在 cache 中且没有线程使用的数据,链表尾保存最近使用的数据。
    • in_use_:保存存放在 cache 中且被使用到的数据,无特定顺序,只有当没有使用到时(ref==1)才会追加到 lru_ 尾部,保证了是按照 least-recently used 而不是 least-recently requested 淘汰。

Snapshot

之前也提到过 leveldb 支持 snapshot:使用 SnapshotList 记录所有的 snapshot,在 compaction 时会保留所有有可能被 snapshot 访问到的数据,ReleaseSnapshot 就是 把该 snapshotSnapshotList 中移除,就不再赘述了。

leveldb 不会持久化 snapshots,因为重启时所有 snapshots 就无效了。

VersionSet

还有很重要的一环是保证读取数据时的有效性,比如读 memtable 时,要保证该 memtable 是有效的;使用 snapshot 时,要保证对应版本的数据要存在;遍历 db 时,要保证提供 一致的数据。memtable 使用引用计数保证了有效性,compaction 的实现保证了 snapshot 对应的数据不会被清理,而对外提供 db 一致的数据就由 VersionSet 保证。

VersionSet 记录了所有的 metadata,同时保存了一个 Version 链表,Version 记录的是当时版本的 sstable 组织结构,只有每次 compaction 完成时才会改变 sstable 的 组织结构并增加一个新的 Version(图来自CatKang): image

VersionVersion 之间的增量就是 manifest 中记录的数据 VersionEdit: image

Version 同样使用引用计数保护,当引用计数为 0 时会从 VersionSet 中移除,leveldb 保证了所有被 Version 使用到的 sstable 文件不会被删除,从而提供了一致的数据。当用 Iterator 遍历 整个 DB 的数据时,就会增加当前 Version 的引用计数。

重启时会根据 manifest 中的 VersionEdit 恢复并只保留最新的 Version,并清理无用数据。

Iterator

leveldb 支持对 db 的遍历,在其内部也实现了多种 Iterator 用于查询、顺序遍历数据,Iterator 为上层提供了统一的接口,封装了底层的存储细节。Iterator 按功能分为如下几类:

  • Iterator:提供最基本的查询数据的能力,用在 memtable 的查询、block 的查询等。
  • TwoLevelIterator:传入一个 Iterator 和函数指针,以 Iterator 的数据作为参数调用函数生成新的 Iterator,相当于传入的是 IteratorIterator,用在 遍历非 0 层的 sstable、遍历 sstable 等。
  • MergingIterator:传入多个 Iterator,用类似 merge sort 的思路,产生有序的结果,用在不同 level 间的 sstable 遍历等。

TwoLevelIteratorMergingIterator 将基本的 Iterator 组合起来,提供了极大的灵活性。以遍历整个 db 为例,会将 memtable Iteratorimmutable memtable Iteratorlevel-0 的各个 sstable Iterator、其他 levelConcatenatingIterator 组合为 MergingIterator 来遍历,需要注意的是,遍历 db 时会有许多版本(sequence)的 key, 需要返回的是能访问到的最新的版本(若不传入 snapshot 则默认 snapshot 为当时最新的)。

分类:

更新时间:

留下评论