05 内存快照:宕机,Redis如何快速恢复?
内存快照:指内存中的数据在某一个时刻的状态以文件的形式写到磁盘上,类似于照片。
快照文件就称为 RDB 文件,其中,RDB 就是 Redis DataBase 的缩写。
两个关键问题:
- 对哪些数据做快照?关系到快照的执行效率问题;
- 做快照时,数据还能被增删改吗?关系到 Redis 是否被阻塞,能否同时正常处理请求。
给哪些内存数据做快照?
Redis 的数据都在内存中,执行的是全量快照,把内存中的所有数据都记录到磁盘中。
RDB 文件的生成是否会阻塞主线程,关系到是否会降低 Redis 的性能。
Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。
- save:在主线程中执行,会导致阻塞;
- bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,是 Redis RDB 文件生成的默认配置。
快照时数据能修改吗?
对内存数据做快照时,数据若能被修改,则 Redis 还能正常处理写操作。否则,所有写操作都得等到快照完了才能执行,降低性能。
你可能会想到,用 bgsave 避免阻塞啊。这里我就要说到一个常见的误区了,避免阻塞和正常处理写操作并不是一回事。此时,主线程的确没有阻塞,可以正常接收请求,但是,为了保证快照完整性,主线程只能处理读操作,因为不能修改正在执行快照的数据。
Redis 写时复制技术(Copy-On-Write, COW),在执行快照的同时,正常处理写操作。
- 如果主线程对数据也都是读操作(键值对 A),主线程和 bgsave 子进程相互不影响。
- 如果主线程要修改一块数据(键值对 C),这块数据就会被复制一份,生成该数据的副本(键值对 C’),主线程在这个数据副本上进行修改。同时,bgsave 子进程可以继续把原来的数据写入 RDB 文件。
多久做一次快照?
bgsave 执行时不阻塞主线程,但如果频繁地执行全量快照,也会带来两方面的开销。
- 频繁将全量数据写入磁盘,会给磁盘带来很大压力。
- bgsave 子进程需要通过 fork 操作从主线程创建出来。子进程在创建后不会再阻塞主线程,但是,fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。
增量快照:
做一次全量快照后,后续的快照只对修改的数据进行快照记录,避免每次全量快照的开销。
Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。
内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。
T1 和 T2 时刻的修改,用 AOF 日志记录,等到第二次做全量快照时,可以清空 AOF 日志,因为此时的修改都已经记录到快照中,恢复时就不再用日志。
关于 AOF 和 RDB 的选择问题,三点建议:
- 数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择;
- 如果允许分钟级别的数据丢失,可以只使用 RDB;
- 如果只用 AOF,优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡。
问:
使用一个 2 核 CPU、4GB 内存、500GB 磁盘的云主机运行 Redis,Redis 数据库的数据量大小差不多是 2GB,使用了 RDB 做持久化保证。当时 Redis 的运行负载以修改操作为主,写读比例差不多在 8:2 左右。你觉得,在这个场景下,用 RDB 做持久化有什么风险吗
答:
内存资源和 CPU 资源两方面分析了风险:
内存不足的风险:Redis fork 一个 bgsave 子进程进行 RDB 写入,如果主线程再接收到写操作,就会采用写时复制。写时复制需要给写操作的数据分配新的内存空间。本在持久化过程中,为了保存 80% 写操作涉及的数据,写时复制机制会在实例内存中,为这些数据再分配新内存空间,分配的内存量相当于整个实例数据量的 80%,整个系统内存的使用量就接近饱和了。此时,如果实例还有大量的新 key 写入或 key 修改,云主机内存很快就会被吃光。
如果云主机开启了 Swap 机制,就会有一部分数据被换到磁盘上,当访问磁盘上的这部分数据时,性能会急剧下降。如果云主机没有开启 Swap,会直接触发 OOM,整个 Redis 实例会面临被系统 kill 掉的风险。
主线程和子进程竞争使用 CPU 的风险:生成 RDB 的子进程需要 CPU 核运行,主线程本身也需要 CPU 核运行,而且,如果 Redis 还启用了后台线程,此时,主线程、子进程和后台线程都会竞争 CPU 资源。由于云主机只有 2 核 CPU,这就会影响到主线程处理请求的速度。