AOF好处是每次执行只需要记录操作命令,记录量不大。但在故障恢复时,需要逐一执行AOF的操作命令,如果日志很大,恢复就很慢。
今天学习另一种持久化方式:内存快照。内存快照,是Redis某一时刻的状态,以文件的形式保存在磁盘上。这个快照文件就称为RDB文件,其中RDB就是Redis Database的缩写。
当故障恢复时,只要把RDB文件读入内存即可,恢复速度很快。但是内存快照并不是最优选项,为什么呢?
我们还需要考虑两个问题:
- 对哪些数据做快照?这关系到快照的执行效率;
- 做快照时,数据还能被增删改吗?这关系到Redis是否被阻塞,能否同时正常处理请求。
对哪些数据做快照?
Redis的数据都在内存中,为了提供所有数据的可靠性保证,它执行的是全量快照。
Redis提供两个命令生成RDB文件:
save
:在主线程中执行,但会导致阻塞;bgsave
:创建一个子进程,专门用于写入RDB文件,避免了主线程的阻塞。也是Redis RDB文件生成的默认配置。
做快照时,数据还能被增删改吗?
如果快照执行期间数据不能被修改,是会有潜在问题的。假如做快照的20s时间里,如果数据不能被修改,Redis就不能处理对这些数据的写操作,那无疑就会给业务服务造成巨大影响。
Redis借助操作系统提供的写时复制技术(Copy-On-Write,COW),在执行快照的同时,正常处理写操作。
bgsave子进程是由主线程fork生成的,可以共享主线程的所有内存数据。
当主线程要修改数据时,通过COW复制一份副本出来给bgsave子进程。
这既保证快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。
可以每秒做一次快照吗?
如果内存快照每隔几分钟执行一次,这样就可能出现丢失数据的风险。那么是否可以每秒做一次快照吗?
如果频繁地执行全量快照,会带来两方面开销:
- 一是频繁将全量数据写入磁盘,会给磁盘带来很大压力。
- 二是bgsave子进程需要从主线程fork出来,fork创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。
既然频繁执行全量快照不行,那还有什么其他好方法吗?
第一个方法是做增量快照。
在第一次做完全量快照后,T1和T2时刻如果再做快照,只需要将被修改的数据写入快照文件就行了。如下图所示:
第二个方法是Redis 4.0提出一个混合使用AOF日志和RDB快照的方法:RDB快照以一定的频率执行,在两次快照之间,使用AOF日志记录这期间的所有命令操作。
T1和T2时刻的修改,用AOF日志记录,等到第二次做全量快照时,就可以清空AOF日志,因为此时的修改都已经记录到快照中了。如下图所示:
拓展
使用一个2核CPU、4GB内存、500GB磁盘的云主机运行Redis,Redis数据库的数据量大小差不多是2GB,使用了RDB做持久化保证。当时Redis的运行负载以修改操作为主,写读比例差不多在8:2左右,也就是说,如果有100个请求,80个请求执行的是修改操作。在这个场景下,用RDB做持久化有什么风险吗?
主要有两方面风险:内存资源风险和CPU资源风险。
内存资源风险
Redis fork子进程做RDB持久化,由于写的比例为80%,那么在持久化过程中,“写时复制”会重新分配整个实例80%的内存副本,大约需要重新分配1.6GB内存空间,这样整个系统的内存使用接近饱和。
如果此时父进程又有大量新key写入,很快机器内存就会被吃光,如果机器开启了Swap机制,那么Redis会有一部分数据被换到磁盘上。
当Redis访问这部分在磁盘上的数据时,性能会急剧下降,已经达不到高性能的标准。
如果没有开启Swap,会直接触发OOM,父子进程会面临被系统kill掉的风险。
CPU资源风险
虽然子进程在做RDB持久化,但生成RDB快照过程会消耗大量的CPU资源。
虽然Redis处理请求是单线程的,但Redis Server还有其他线程在后台工作,例如AOF每秒刷盘、异步关闭文件描述符这些操作。
由于机器只有2核CPU,这也就是意味着父进程占用了超过一半的CPU资源。
此时子进程做RDB持久化,可能会产生CPU竞争,导致的结果是父进程处理请求延迟增大,子进程生成RDB快照的时间也会变长,整个Redis Server性能下降。
如果Redis进程绑定了CPU,那么子进程会继续父进程的CPU亲和性属性,子进程必然会与父进程争夺同一个CPU资源,整个Redis Server的性能必然会受到影响。
因此如果Redis需要开启定时RDB和AOF重写,进程一定不要绑定CPU。