LevelDB 源码阅读:LRU Cache 高性能缓存实现细节

0 Comment

计算机系统中,缓存无处不在。从 CPU 缓存到内存缓存,从磁盘缓存到网络缓存,缓存无处不在。缓存的核心思想就是空间换时间,通过将热点数据缓存到高性能的存储中,从而提高性能。因为缓存设备比较贵,所以存储大小有限,就需要淘汰掉一些缓存数据。这里淘汰的策略就非常重要了,因为如果淘汰的策略不合理,把接下来要访问的数据淘汰掉了,那么缓存命中率就会非常低。

缓存淘汰策略有很多种,比如 LRU、LFU、FIFO 等。其中 LRU(Least Recently Used) 就是一种很经典的缓存淘汰策略,它的核心思想是:当缓存满了的时候,淘汰掉最近最少使用的数据。这里基于的一个经验假设就是”如果数据最近被访问过,那么将来被访问的几率也更高“。只要这个假设成立,那么 LRU 就可以显著提高缓存命中率。

在 LevelDB 中,实现了内存中的 LRU Cache,用于缓存热点数据,提高读写性能。默认情况下,LevelDB 会对 sstable 的索引 和 data block 进行缓存,其中 sstable 默认是支持缓存 990 (1000-10) 个,data block 则默认分配了 8MB 的缓存。

LevelDB 实现的 LRU 缓存是一个分片的 LRU,在细节上做了很多优化,非常值得学习。本文将从经典的 LRU 实现思路出发,然后一步步解析 LevelDB 中 LRU Cache 的实现细节。

阅读全文

LevelDB 源码阅读:MemTable 内存表的实现细节

0 Comment

在 LevelDB 中,所有的写操作首先都会被记录到一个 Write-Ahead Log(WAL,预写日志)中,以确保持久性。接着数据会被存储在 MemTable 中,MemTable 的主要作用是在内存中有序存储最近写入的数据,到达一定条件后批量落磁盘。

LevelDB 在内存中维护两种 MemTable,一个是可写的,接受新的写入请求。当达到一定的大小阈值后,会被转换为一个不可变的 Immutable MemTable,接着会触发一个后台过程将其写入磁盘形成 SSTable。这个过程中,会创建一个新的 MemTable 来接受新的写入操作。这样可以保证写入操作的连续性,不受到影响。

在读取数据时,LevelDB 首先查询 MemTable。如果在 MemTable 中找不到,然后会依次查询不可变的 Immutable MemTable,最后是磁盘上的 SSTable 文件。在 LevelDB 的实现中,不管是 MemTable 还是 Immutable MemTable,内部其实都是用 class MemTable 来实现的。这篇文章我们来看看 memtable 的实现细节。

阅读全文

LevelDB 源码阅读:结合代码理解多版本并发控制(MVCC)

0 Comment

在数据库系统中,并发访问是一个常见的场景。多个用户同时读写数据库,如何保证每个人的读写结果都是正确的,这就是并发控制要解决的问题。

考虑一个简单的转账场景,开始的时候 A 账户有 1000 元,要转 800 元给 B 账户。转账过程包括两步:从 A 扣钱,给 B 加钱。恰好在这两步中间,有人查询了 A 和 B 的余额。

如果没有任何并发控制,查询者会看到一个异常现象:A 账户已经被扣除了 800 元,只剩 200 元,B 账户还没收到转账,还是原来的金额!这就是典型的数据不一致问题。为了解决这个问题,数据库系统需要某种并发控制机制

最直观的解决方案是加锁,当有人在进行写操作(如转账)时,其他人的读操作必须等待。回到前面的问题,只有在转账的两步都完成之后,才能查到正确的账户余额。但是锁机制存在明显的问题,每次只要写相关 key,所有读该 key 的操作都要排队等待,导致并发上不去,性能会比较差。

现代数据库系统普遍采用 MVCC 来控制并发,LevelDB 也不例外,接下来我们结合源码来理解 LevelDB 的 MVCC 实现。

阅读全文