redis是基于内存的数据库。总所周知,内存是RAM,数据断电即丢失。对于数据库来说,这种特性导致了数据持久性就无法保证。redis的持久化的机制解决这种问题,就是把数据库的数据保存到硬盘中,进行存储,避免数据丢失。
redis有两种持久化的方式:RDB和 AOF
一、RDB
RDB是redis默认的持久化方式。将当前数据库的状态,以快照的形式保存在硬盘上。但如果开启了AOF的话,redis优先使用AOF进行持久化。因为一般情况下,AOF的持久化的频率更高,即使出现问题,丢失数据量会更少。
1.1 RDB工作流程
redis服务器有两种方式创建rdb文件:SAVE 、BGSAVE
1.1.1 SAVE
在服务器收到SAVE命令后,会由服务器主进程创建rdb文件。此时,服务器是阻塞状态的,客户端的任何请求都会被拒绝。只有主进程执行完SAVE命令后,才会重新接收客户端的请求
1.1.2 BGSAVE
在服务器收到BGSAVE命令后,主进程会创建一个子进程。由子进程完成rdb文件的创建,并不阻塞服务器主进程,所以服务器可以继续接受客户端的请求。
在执行BGSAVE期间,服务器继续接收请求命令。但如果请求命令是SAVE、BGSAVE、BGREWRITEAOF的话,服务器如何进行处理呢?针对这个问题,服务器有如下方式进行解决:
1. 如果客户端发生的是SAVE 或者 BGSAVE 的话,服务器直接拒绝本次请求
2.redis服务器不允许BGREWRITEAOF 和 BGSAVE 同时执行:如果执行BGREWRITEAOF时,客户端发送BGSAVE命令,服务器直接拒绝请求;如果执行BGSAVE时,客户端发送BGREWRITEAOF命令,服务器会把BGREWRITEAOF命令推迟到BGSAVE执行完成后再执行。
二、AOF持久化
AOF是通过记录数据库的写命令来记录数据库状态的。如图

2.1 AOF持久化的实现
AOF持久化主要分为三个步骤:命令追加、文件写入、文件同步
2.1.1 命令追加
当AOF功能开启后,服务器在执行完成一个写命令后。会以协议格式将被执行的写命令追加到服务器的aof_buf缓冲区(aof缓冲区)的末尾。
2.1.2 文件写入与文件同步
redis服务器进程本身就是一个事件循环,主要负责接收客户端的请求命令和向客户端发送命令回复。在一次事件循环期间,可能会执行写命令,那么有一些内容就被追加到aof_buf缓冲区里面。所以在服务器每次结束一个事件循环之前,它调用flushAppendOnlyFile函数,把aof缓冲区中的内容写入和保存到AOF文件中。
2.1.3 AOF文件的载入与数据还原
redis把数据持久化到AOF文件中了,那么怎么还原数据呢?还原数据的步骤:
1.创建伪客户端:因为redis的命令只有在redis客户端上下文中才能执行,所以需要一个伪客户端来执行aof文件中的命令
2.逐条读取aof文件中的命令,并由伪客户端执行。一直到AOF文件中的命令处理完成为止。aof文件载入的流程图如下:

2.1.4 AOF重写
aof持久化的原理如上讲解,但这种机制却有一个致命的问题。那就是aof文件中的数据冗余,随着时间推移aof文件会越来越大,如果不及时解决这个问题会严重redis服务器的性能。举例子说明:
如果执行以下代码:
rpush friuts ‘apple’ "banana" "orange"
lpop friuts
lpop friuts
lpop friuts
执行结束后,其实fruits 列表已经为空了,但是redis的aof会把这四条语句全部记录下来。就aof还原来说,这四条写命令是做的是无用功:不仅不会影响数据的最终结果,又消耗存储aof文件的存储资源,降低了数据还原性能。
为了解决这种aof文件体积膨胀的问题,redis提供了aof文件重写的机制。基于当前数据库的数据创建出一个新的aof文件,来替换现有的aof文件。新旧两个aof保存的数据库状态相同,但是新的aof文件还没有记录下冗余命令,就可以降低aof文件的空间了。随着时间推移,这个新的aof也会不断记录下冗余命令,也会逐渐膨胀,又会再创建新的aof文件代替它。
2.1.5 AOF重写的实现
AOF重写是针对当前数据库的状态进行分析的,并不是对现有aof进行处理的。
Redis的aof重写是在子进程中进行的,这样设计有两大好处:
1. 不阻塞服务器进程(父进程),服务器可继续处理请求
2.采用的是子进程,而不是线程。那么就不会有线程的数据不安全性问题
aof重写的具体步骤:当redis服务器创建aof重写的子进程时,也会开启一个AOF重写缓冲区(注意:不是AOF缓冲区,AOF缓存是实现aof持久化的)。在子进程进行AOF重写时,redis执行完一条写命令后,会把这条写命令同时写入AOF缓冲区和AOF重写缓冲区。当子进程完成AOF重写后,子进程会向父进程发送完成新信号,父进程把AOF重写缓冲区的命令(这些命令属于子进程执行期间,redis服务器处理的新命令)追加到新创建的aof文件内,然后对新创建的aof文件进行改名,原子地完成对现有aof文件的覆盖,实现新旧AOF文件的替换。
针对上面描述,可以知道:
- 子进程只对当前数据库的状态写入新aof文件中。对于AOF重写缓冲区的命令,由父进程追加到新AOF文件中;
- 为什么要设置两个缓冲区?aof缓冲区是为了保证对原aof的持久化;aof重写缓冲区是对aof重写期间,处理的写命令进行的记录。那么只有重写缓冲区不就够了吗,为什么需要对原aof文件的维护?如果不对原aof文件的维护,一旦重写失败,重写期间的写命令就得不到记录,会造成数据的丢失。
三、尚存在的问题
虽然持久化尽可能的保证了数据不会丢失,但这也是100%的保证。也就是说还是会存在少量数据丢失的情况。举个例子:对于rdb持久化的方式,当某次完成了持久化后,还没有进行下一次rdb持久化。掉电了或者出现其他的情况,导致服务器宕机,那么在最近一次的持久化之后的数据就会丢失。