AOF 记录的是操作命令,而不是实际的数据,所以使用 AOF 方法进行故障恢复的时候,需要逐一把操作日志都执行一遍。如果操作日志非常多,redis 就会恢复得很缓慢,影响到正常使用。所以,redis 的另一种持久化方法:RDB 内存快照。内存快照是指内存中的数据在某一个时刻的状态记录,它是以文件的形式写到磁盘上,这样即使宕机了,快照文件也不会丢失,可靠性也就得到了保证。 但引入 RDB 也会有问题:
- 对哪些数据做快照?这会关系到快照的执行效率问题;
- 做快照时,数据还能被增删改吗?这会关系到 redis 是否被阻塞,能否同时正常处理请求。
给哪些内存数据做快照?
redis 的数据都在内存中,为了提供所有数据的可靠性保证,它执行的是全量快照,就是把内存中的所有数据都记录到磁盘中。但是,随着全量数据越多,RDB 文件就越大,往磁盘上写数据的时间开销就越大。 由于 redis 单线程模型,我们要尽量避免所有会阻塞主线程的操作。redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。
- save:在主线程中执行,会导致阻塞;
- bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 redis RDB 文件生成的默认配置。
快照时数据能修改吗?
理想情况下,生成快照时数据最好不要“动”,不能被修改。但是如果 redis 不能对数据进行写操作,那就会给业务造成巨大的影响。虽然 bgsave 是在后台执行,能避免阻塞,但避免阻塞和正常处理写操作不是一回事。 此时的主线程确实没有阻塞,但它只能接收读操作,因为不能修改正在执行快照的数据。 如果为了快照而暂停写操作,这是不能接受,所以 redis 借助了操作系统提供的写时复制技术(copy-on-write,cow),在执行快照的同时,能正常处理写操作。 bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。此时如果主线程对这些数据也是读操作,那么主线程和 bgsave 子进程互不影响。但是如果主线程想要修改一块数据,那么这块数据就会被复制一份,生成该数据的副本,然后主线程对这个数据副本进行修改。同时 bgsave 子进程可以继续把原来的数据写入 RDB 文件。 这既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。
多久做一次快照?
比如,T0 时刻做了一次快照,T0 + t 时刻又做了一次快照。如果在 t 这段时间内,机器宕机了,那么只能 T0 时刻的快照进行恢复。此时就存在数据的丢失。要想尽可能恢复数据,t 就要尽可能小。但如果频繁地执行全量快照,也会带来开销:
- 一方面,频繁将全量数据写入磁盘,会给磁盘带来很大的压力,前一个快照还没做完,后一个快照就开始了,造成恶性循环;
- 另一方面,bgsave 子进程需要通过 fork 操作从主线程创建出来。虽然,子进程创建后不会阻塞主线程,但是,fork 这个创建过程本身会阻塞主线程,而主线程内存越大,阻塞时间越长。如果频繁 fork ,这就会阻塞主线程了。(所以在 redis 中如果有一个 bgsave 运行,就不会再启动第二个 bgsave 子进程)
此时,可以做增量快照,就是做了一次全量快照后,后续的快照只是对修改的数据进行快照记录,这样可以避免每次全量快照的开销。如下图所示: 但这需要我们使用额外的元数据信息去记录哪些数据被修改了,这会带来额外的空间开销。为了“记住”修改,引入额外的空间开销比较大,这对内存资源宝贵的 redis 来说,得不偿失。 那还有什么方法既能利用 RDB 的快速恢复,又能以较小的开销做到尽量少丢数据? redis4.0 中提出了混合使用 AOF 日志和内存快照的方法。内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。 这样,快照就不用频繁的执行,避免了频繁 fork 对主线程对影响。而且 AOF 只用记录两次快照间的操作,不需要记录所有操作,就不会出现文件过大的情况,避免重写开销。如下图所示: T1 和 T2 时刻的修改,用 AOF 日志记录,等到第二次全量快照时,就可以清空 AOF 日志,此时的修改都记录到了快照,恢复时就不用日志了。 这个方法既能享受 RDB 快照快速恢复到好处,又能享受 AOF 只记录操作命令的简单优势。
|