Redis的持久化机制:RDB内存快照

234 阅读4分钟

RDB 与 AOF 的区别:

AOF 文章中说到,AOF 日志记录的是操作命令而不是实际的数据,也正因为如此,在进行数据恢复的时候,需要依次执行日志中的命令,在操作命令数据量很大的情况下,执行命令将会变得非常慢,影响正常使用。而 RDB 内存快照,记录的是在某一个时刻的内存中的数据,而不是操作命令。它把某一时刻的状态以文件的形式写到磁盘上,这样一来,即使宕机,快照文件也不会丢失,数据的可靠性也就得到了保证。这个快照文件就称为 RDB 文件,其中,RDB 就是 Redis DataBase 的缩写。

给哪些内存数据做快照?

Redis 提供了两个命令来生成 RDB 文件,分别是 savebgsave。save:在主线程中执行,会导致阻塞;bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。

为了保证数据不丢失,可能我们首先会想到的是全量快照,也就是将内存中的所有数据都写入磁盘中,这样便一次性记录了所有的数据。但是,全量快照也会花费很多时间。而且,全量数据越多,RDB 文件就越大,往磁盘上写数据的时间开销就越大。

基于 Redis 的单进程模型,我们要尽量避免所有会阻塞主线程的操作,所以这个时候我们就可以通过 bgsave 命令来执行全量快照,这既提供了数据的可靠性保证,也避免了对 Redis 的性能影响。同时,bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据,这既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。

如何选择快照频率?

如果频繁地执行全量快照,会带来两方面的开销。

一方面,频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环。另一方面,bgsave 子进程需要通过 fork 操作从主线程创建出来。虽然,子进程在创建后不会再阻塞主线程,但是,fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。如果频繁 fork 出 bgsave 子进程,这就会频繁阻塞主线程了(所以,在 Redis 中如果有一个 bgsave 在运行,就不会再启动第二个 bgsave 子进程)。

此时,我们可以做增量快照,所谓增量快照,就是指,做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。这么做的前提是,我们需要记住哪些数据被修改了。如果我们对每一个键值对的修改,都做个记录,那么,如果有 1 万个被修改的键值对,我们就需要有 1 万条额外的记录。而且,有的时候,键值对非常小,比如只有 32 字节,而记录它被修改的元数据信息,可能就需要 8 字节,这样的话,为了“记住”修改,引入的额外空间开销比较大。这对于内存资源宝贵的 Redis 来说,有些得不偿失。

到这里,我们可以发现,虽然跟 AOF 相比,快照的恢复速度快,但是,快照的频率不好把握,如果频率太低,两次快照间一旦宕机,就可能有比较多的数据丢失。如果频率太高,又会产生额外开销,那么,还有什么方法既能利用 RDB 的快速恢复,又能以较小的开销做到尽量少丢数据呢?

Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。这个方法既能享受到 RDB 文件快速恢复的好处,又能享受到 AOF 只记录操作命令的简单优势。