白话rocksdb

| 分类 rocksdb  | 标签 原理 

读写流程

不同于InnoDB, RocksDB使用日志合并树模型(LSM)。往RocksDB中写数据时(启用memtable和WAL),先写WAL,然后写memtables。memtables写满了,会触发后台任务, 将写满的memtables中的数据flush到磁盘,生成sst文件到第0层, sst文件一旦生成,其内容就不再改变。 同时,对数据库结构的修改,会触发后台任务运行,从上到下(层数从0开始编号, 从小到大),计算每一层中是否有文件需要合并,如有sst文件满足合并条件,这些文件会被合并,生成新的sst文件,放到合适的层中,可能是当前层,也可能是下一层。可以注意到,RocksDB中数据的持久化和数据文件的合并,只涉及到如下文件操作:生成一个新文件,读取若干文件,删除已存在文件,因此只有追加写,没有随机写。RocksDB的写性非常好。

从RocksDB中读取数据,先去memtables中去读,如果memtables中没有,就去第0层中读,如果还未命中,就从第1层起,逐层找。RocksDB维护了每层中key的范围, 且除了第0层,其他每层中任意两个文件的key范围是不重叠的,且任意一个sst文件中key都是有序的。 由于memtables和第0层的数据都位于内存,且通常不大; 而每层中每个文件所包含的key是已知的,所以能很快找到key所在的目标文件, 因此查找操作也能有相当好的性能。另外,需要注意的时,如果在上层中找到目标key,就不需要到更下面的层中去找了。

Manifest文件

从上面的读写流程我们知道,为了快速从RocksDB中读取某个key的数据,或者快速定位哪些文件需要合并,需要维护一些常驻内存的信息, 也就是整个LSM树的结构, 以加速查找和合并;同时,LSM的结构,在数据库重启时,需要能够快速重建,因此需要把这些信息持久化,Manifest文件就是这些信息的持久化存在。 RocksDB官方文档里,对Manifest文件的描述是: Manifest是一个记录了数据库状态变化的事务性日志。关注两点,一是事务性日志,二是记录了数据库状态的变化,也就是说,Manifest文件的记录着改变数据库状态的操作。从Ref 2中可以知道,Manifest文件中记录了诸如新增sst文件到某一层,当前日志文件名,之前日志文件名,当前Manifest文件名等操作。 每一个操作都将数据库置为一个新的状态,也就是一个新的版本。Manifest文件的更新在VersionSet::LogAndApply中实现,可以通过定位这个方法的调用点查看在哪些情况下Manifest文件被更新了。 Manifest文件作为一个事务性日志文件,只要数据库有变化,其状态就会增长,因此Manifest达到一定大小会被VersionSet::WriteSnapshot重写。

现在,我们回到之前的问题,在进程Crash后如何恢复数据库。简言之,就是两个恢复,第一步,读取Manifest文件,在内存中重建LSM树的结构; 第二步,恢复WAL日志文件。 因为在进程Crash时,memtables中的数据可能并没有刷盘,因此需要从WAL中恢复。

版本号和快照

我们知道,Manifest文件的任一时刻的状态,都代表着RocksDB的一个状态,这是一个比较宏观的状态。 我们知道Manifest的每一次更新都意味着RocksDB进入一个新的状态,但是进入了哪一个状态,我们是不知道的。 而事实上,对RocksDB的每一次写操作,都会形成一个版本,这个版本号为一个8字节的整数,作为key的一部分写入到数据库中去, 具体而言是追加到key后面; 由于RocksDB中所有的key是有序的,因此同一个key的所有版本都会聚集在一起。 有了精确到每个写操作的版本号,很多事情都好办了,最直接的就是快照的实现。 例如,要获取RocksDB某个版本的数据,只需要获取并保存当前数据库的全局版本号,并将其标记为快照即可,后续所有的覆盖写,删除,和合并操作时,都会维持每个快照数据的一致性。

物理备份

RocksDB使用数据文件(sst 文件) + 日志文件, 来记录数据,同时使用Manifest文件记录LSM树的结构信息。基于这些条件,RocksDB可以提供point-of-time的数据备份功能, 具体API可参考官方文档。 实现原理原理如下:

  • 禁止RocksDB删除文件 (sst文件和日志文件)
  • 获取所有在RocksDB中被使用的文件(sst文件, CURRENT文件,配置文件,以及Manifest文件)
  • 将所有的文件拷贝到指定的备份目录
  • 允许RocksDB删除文件

我们知道, sst文件一旦生成就不会改变,只可能在合并时被删除,因此一旦禁止了文件的删除,在使用Manifest文件重建LSM结构时,所有需要的sst文件和WAL文件都会存在, (当然,有些文件可能不需要)。恢复了LSM结构信息后,通过恢复WAL,可以获取在做备份时的memtables内容。 这里,需要注意的是,本质上,RocksDB备份是文件拷贝,耗时长短取决于文件的数量和大小;而在文件拷贝的过程中,写入不会被阻塞,因此WAL的内容在备份过程中会发生改变。RocksDB提供了一个选项,flush_before_backup来控制是否也要拷贝WAL, 如果flush_before_backup被设置为true,备份期间不拷贝WAL文件,因为memtables中的数据已经被耍到了sst中。 这个选项对于控制备份的状态很有用, 例如,对于测试备份恢复的正确性,很有帮助, 因为测试者可以精确记录在调用backup之前做了哪些写操作,同时在backup的过程中,继续写入数据。

小结

RocksDB是LevelDB的变种,是一个基于LSM的key/value的存储引擎,具有很多优秀的特性, 已经在业界有了广泛的使用,如开源的tidb和cockroachdb, 脸书的MyRocks,以及头条,陌陌等公司的一些优秀实践,值得关注。本文简单介绍了一些基本原理。RocksDB本身的实现还是比较复杂的, 使用方式也非常灵活, 限于篇幅,这里不过于深入细节。 欢迎讨论。


上一篇     下一篇