RDB 快照
本文内容
1. 为什么需要 RDB 快照?
在上一章中讲解了 Redis 的一种持久化方案 — AOF 日志,它记录了操作的命令,在 AOF 文件过大时会进行 AOF 重写来压缩文件;而且提供了三种刷盘策略,来保证数据丢失和阻塞主线程之间的平衡。那为什么还需要 RDB 快照呢?
这就要从 AOF 日志的格式说起,AOF 日志中记录的是 逻辑日志,而 不是实际的数据,所以使用 AOF 进行故障恢复时,需要把日志命令都执行一遍,如果日志很多,Redis 恢复的时间就会很长,影响正常使用。这时候就需要 RDB 快照了。
2. 什么是 RDB 快照?
RDB(Redis DataBase)快照,就是一个 内存快照,可以 把内存中某个时刻的数据记录下来,当然了,也是记录到磁盘上,这样就算宕机,快照文件也不会丢失。
与 AOF 不同,RDB 记录的是某个时刻的数据,而不是命令,所以是 真实的物理数据。在做数据恢复时,可以直接把 RDB 文件读入内存,很快就能完成数据恢复。
因为 Redis 的数据都在内存,在进行 RDB 快照时是 全量快照,也就是会 把内存中的数据全部记录到磁盘中。
由于是全量快照,所以当数据量很大时,执行 RDB 快照是非常耗时的,此时肯定不能阻塞主线程,Redis 也提供了两个命令来生成 RDB 文件:
- save:主线程执行,会导致 Redis 阻塞;
- bgsave:fork 一个 子进程 执行,不会阻塞主线程,这也是默认配置。
疑问:既然全量快照很耗时,那能不能实现增量快照呢?
要实现增量快照,那在这次 RDB 快照生成后,后续就需要记录哪些数据被修改了,而这 需要额外增加元数据信息去记录,会带来不小的开销。而且如果一个键值对本身很小,为了记录修改而需要的额外元数据就会显得占比很大,内存对于 Redis 是非常宝贵的,所以有些得不偿失了,因此 Redis 也并没有实现增量快照的功能。
既然 RDB 快照不是一条条命令,这就意为着不会随每条命令执行时写入文件,那什么时候进行 RDB 快照呢?执行快照时 Redis 还能否处理请求?这些问题都是 RDB 快照需要解决的。
3. 什么时候进行 RDB 快照?
RDB 记录的是某一时刻的数据,所以进行 RDB 快照的时机就非常重要了,生成 RDB 的间隔越小,比如 1 秒生成一次,那数据丢失的就越少。
bgsave 生成 RDB 快照时不会阻塞主线程,这意味着我们可以让 RDB 快照生成的时间间隔越小越好吗?
其实也并不是,虽然 bgsave 不会阻塞主线程,但频繁执行全量快照,会带来下面的问题:
- 频繁将全量数据写入磁盘,会给磁盘带来很大压力,可能前一个还没做完,后一个又开始了,导致恶性循环;
- bgsave 需要由主线程 fork 出子进程,与 AOF 重写类似,fork 这个过程会阻塞主线程,数据越多,fork 过程越长。所以 频繁执行 bgsave 会导致频繁 fork 子进程,导致频繁阻塞主线程(所以 Redis 中只能有一个 bgsave 在运行)。
其实,Redis 提供了配置来规定多久执行一次 bgsave,默认提供的配置如下:
save 900 1
save 300 10
save 60 10000
注:虽然配置项叫 save,但之际执行的是 bgsave,并不会阻塞主线程。
这三个配置的含义分别如下:
- 900 秒内,至少进行了 1 次修改操作;
- 300 秒内,至少进行了 10 次修改操作;
- 60 秒内,至少进行了 10000 次修改操作。
只要满足上面条件的任意一个,就会执行 bgsave,对 Redis 进行全量快照。
所以我们可以根据自己业务能接受数据丢失的实际情况,来进行配置。
4. 进行快照时能修改数据吗?
在执行 RDB 快照时,如果数据还在频繁的变动,那肯定会影响快照的完整性,毕竟快照需要消耗挺长时间。
例如,在 t1 时刻执行内存快照,需要 10s 才能做完,在这 10s 内陆陆续续有数据刷入磁盘,而此过程中,比如 t1 + 3s 时刻某些数据发生了修改,即 t1 + 3s 相比与快照开始时刻 t1 来说,数据不同了,就会导致数据的不完整,因为 这些被修改的数据已经不是 t1 时刻的状态了。
所以,在执行快照期间,自快照开始时刻起,内存中的数据就不能被修改了。那这就意味着快照期间 Redis 不能处理写请求了吗?
很显然 Redis 不会这么做,因为 RDB 快照还是挺耗时的,如果不允许执行写请求,那将大大降低 Redis 的性能。
看到这里,有没有觉得这个场景和 AOF 重写 时很像?AOF 重写也是需要根据当前时刻的数据,进行全量重写。那 AOF 重写时能进行写操作,执行 RDB 快照时为什么就不行?
其实,这里采用的技术与 AOF 重写相同,都是 写时复制(Copy-On-Write,COW)技术。
具体来说,bgsave 执行时,主线程会 fork 出一个子进程,这个过程也是只会复制页表,通过页表达到共享内存的效果,bgsave 子进程此时就可以读取内存中的数据,写入 RDB 文件。当主线程发生写操作时,就发生了 COW,此时主线程会真正拷贝出一块物理内存,在这块新的物理内存上进行修改操作,而 bgsave 子进程读取的还是原来的内存,不会影响 RDB 的生成。
图示如下所示:
通过 COW,既保证了快照的完整性,也允许主线程对数据进行修改。
当然了,在快照过程中修改后的数据,是不在 RDB 文件中的,也就是说 会丢失最新数据。
5. RDB 和 AOF 的优劣
学习完 RDB 后,可以发现 RDB 有自己的优势,当然也有劣势,下面就来看看 RDB 和 AOF 的优劣对比。
从 数据恢复的速度 上看:
- RDB 快照 记录的是实际真实的 物理数据,恢复时可以直接读取到内存,速度速度 很快;
- AOF 日志 记录的是命令、逻辑日志,恢复时还需要执行命令,恢复速度 较慢。
从 数据丢失情况 上看:
- RDB 快照丢失数据的情况 和快照的频率有关,执行快照的频率越快,丢失的数据就越少,但是频率并不好控制,频率太快也会导致写磁盘、fork 子进程带来的性能消耗;
- AOF 日志提供了 三个刷盘时机参数 来平衡数据丢失和性能的平衡,当刷盘时机为 Always 时,基本不会出现丢失数据的情况。
可以看出,RDB 快照的最大优势是恢复速度快,但快照的频率不好控制;AOF 快照的最大优势是丢失数据少,但恢复速度较慢。
那有什么方法既能利用 RDB 的快速恢复优势,又能以较小的开销做到尽量少丢数据呢?答案是 Yes,Redis 4.0 中提出的 混合持久化 就可以实现,我们下篇文章见。
6. 参考文章
- 《Redis 核心技术与实战》