在 Redis 中的三种不同方式的持久化策略中,除了 Always 持久化策略性能不太好以外,其他两个策略的性能方面没有太大的影响,但是由于采用的 AOF 日志记录的是操作命令本身,并没有记录键值对数据,因此在数据恢复的时候就需要根据 AOF 日志中的操作一遍一遍地执行,其恢复的效率很低。那么在 Redis 中有没有即可靠性高,又可以在宕机的时候,实现快速恢复数据?
一、RDB 快照持久化方式
Redis 在持久化功能上有两种实现方式,一种是上篇说到的 AOF 日志,另一种就是 RDB 内存快照的方式。RDB 也就是 Redis DataBase 的简称。也就是将内存中某一时刻的数据的状态记录下来做成一个快照,其中存储的就是键值对数据本身,而不是 AOF 日志中记录的操作命令。
1、生成 RDB 文件中记录的数据的范围是多大?
由于 RDB 记录的是某一时刻内存中的数据,因此可以直接将磁盘中记录的 RDB 快照文件读入内存即可完成该快照时刻的数据恢复,因此 RDB 快照记录的是内存中的所有数据。
2、生成 RDB 文件会不会阻塞主线程?
由于 RDB 文件记录的是某一时刻内存中的所有数据,因此需要长时间的进行磁盘写操作,如果 RDB 操作会阻塞主线程,就会导致 Redis 长时间不能正常地提供服务,使其 Redis 的性能不好,而 Redis 提供了两种命令来生成 RDB 文件。
- save(保存):使用主线程来执行 RDB 操作,会长时间地阻塞主线程。
- bgsave:主线程 fack 一个子进程,让子进程负责生成 RDB 文件,这样就不会长时间地阻塞主线程,这也是 Redis 默认生成 RDB 的配置。
3、生成 RDB 文件的时候,其内存中的数据可以被修改吗?
当我们使用 basave 命令来生成 RDB 文件的时候,虽然不会阻塞主线程,但是不会阻塞主线程并不代表主线程可以正常地处理写操作。当我们生成 RDB 文件的期间,我们是不能让数据发生修改的,这样生成的 RDB 文件中可能存在不是该快照的开启时刻状态下的数据,记录下来之后写操作修改后的数据,导致当进行数据恢复的时候,恢复的数据库与实际需要恢复时刻的数据库不一致。因此不阻塞主线程仅仅只是主线程可以处理读取数据的操作,并不能执行更新数据的操作。
4、Redis 中如何解决生成快照期间不能执行写操作的约束?
如果为了生成一次 RDB 文件,使得 Redis 长时间不能执行写操作这往往是不能接受的,因此在 Redis 中就采用了 COW 写时复制机制(Copy-On-Write),当主线程 fark 出 bgsave 子进程来执行 RDB 快照的生成的时候就让 bgsave 子进程复制了主线程的页表,该页表中存储的是主线程中所有的数据块与内存中的物理地址的映射,当主线程执行写操作的时候,首先开辟一块新的内存空间用于存储需要操作的键值对,并修改了自己的页表映射,因此主线程在执行写操作的时候操作地其实是新的拷贝的物理地址,而由于 bgsave 中仍然保存的是开启生成 RDB 快照时主线程的页表,因此 bgsave 中的页表中该数据映射的物理地址还是原来的没有被修改过的,这样就可以满足生成快照读取到的数据不发生改变的需求,同时不阻塞主线程执行写操作。Redis 可以继续正常完成业务。
5、Redis 多久进行一次生成快照?
如果生成快照之间的时间间隔越短,那么进行数据恢复导致丢失的数据就越少,但是生成快照的时间间隔越短,同样会给磁盘带来的压力比较大,同时用于生成快照的 bgsave 子进程也是由主线程 fork 出来的,同样也会频繁的阻塞主线程。
- 那么如果考虑使用低频的全量快照和高频的增量备快照来解决效果会如何?
- (1) 使用低频的进行生成全量快照,然后对于两个快照之间的修改操作进行增量快照,但是这就导致我们需要额外的元数据去记录哪些数据发生了修改,这样就带来了额外的空间开销,例如两个全量快照之间进行了一万次的修改操作,我们就要使用一万个元数据去记录被修改的数据,而且通常键值对需要消耗的内存就比较小,例如只消耗32字节,而记录该键值对被修改的记录就要消耗8字节,对于依赖于宝贵的内存资源的 Redis 而言这样做是不可取的。
- 因此在 Redis4.0 版本中就提出了混合使用 AOF 和 RDB 来实现持久化功能。
- (1) 低频的让主线程 fork 后台子进程 bgsave 来生成 RDB 文件。
- (2) 对于两次生成的 RDB 文件之间的时间间隔中采用 AOF 日志的方式记录操作命令。
- (3) 当进行第二次全量快照的时候,就可以清空 AOF 日志中的操作命令记录。
- (4) 这样一来 AOF 只需要记录短时间内的命令操作,避免了 AOF 文件过大的弊端,又避免了高频的生成 RDB 文件带来的磁盘压力,内存开销及其对主线程的频繁阻塞。