AOF 文件是以追加的方式,逐一记录接收到的写命令的。当一个键值对被多条写命令反复修改时,AOF 文件会记录相应的多条命令。如上示例,我们执行完命令后,Redis会在AOF里面追加5条命令。但实际上只需要set testKey world一条命令就够了。
AOF 重写机制就是在重写时,Redis 根据数据库的现状创建一个新的 AOF 文件,也就是说,读取数据库中的所有键值对,然后对每一个键值对用一条命令记录它的写入。比如说,当读取了键值对“testkey”: “world”之后,重写机制会记录 set testkey world这条命令。这样,当需要恢复时,可以重新执行该命令,实现“testkey”: “world”的写入。
这样,重写后的日志,从5条变成了1条,而对于可能被修改过成百上千次的键值对来说,重写能节省的空间就更大了。
虽然 AOF 重写后,日志文件会缩小,但是,要把整个数据库的最新数据的操作日志都写回磁盘,仍然是一个非常耗时的过程。这时,我们不得不关注:重写会不会导致阻塞?这就要看看AOF重写的过程是怎么样的
AOF 重写过程因为AOF重写也是一个非常耗时的过程,又因为Redis单线程的特性,同内存快照一样,AOF重写的过程也是由父进程fork出bgrewriteaof子进程来完成的.
使用子进程(而不是开启一个线程)进行AOF重写虽然可以避免使用锁的情况下,保证数据安全性,但是会带来子进程和父进程一致性问题。
例如在开始重写之后父进程又接收了新的键值对此时子进程是无法知晓的,当子进程重写完成后的数据库和父进程的数据库状态是不一致的。
如下表:
时间 服务器进程(父进程) 子进程T1 执行命令 SET K1 V1
T2 执行命令 SET K1 V1
T3 创建子进程,执行AOF文件重写 开始AOF重写
T4 执行命令 SET K2 V2 执行重写
T5 执行命令 SET K3 V3 执行重写
T6 执行命令 SET K4 V4 完成AOF重写
在T6时刻服务器进程有了4个键,而子进程却只有1个键
为了解决这种不一致性,Redis设置了一个AOF重写缓冲区。
在子进程执行AOF重写期间。服务器进程需要执行以下3个动作:
执行客户端命令
执行后追加到AOF缓冲区
执行后追加到AOF重写缓冲区
子进程完成AOF重写后,它向父进程发送一个信号,父进程收到信号后会调用一个信号处理函数,该函数把AOF重写缓冲区的命令追加到新AOF文件中然后替换掉现有AOF文件。父进程处理完毕后可以继续接受客户端命令调用,可以看出在AOF后台重写过程中只有这个信号处理函数会阻塞服务器进程。
下表是完整的AOF后台重写过程:
T1 执行命令 SET K1 V1
T2 执行命令 SET K1 V1
T3 创建子进程,执行AOF文件重写 开始AOF重写
T4 执行命令 SET K2 V2 执行重写
T5 执行命令 SET K3 V3 执行重写
T6 执行命令 SET K4 V4 完成AOF重写,向父进程发送信号
T7 接收到信号,将T5 T6 T7 服务器的写命令追加到新的AOF文件末尾
T8 用新的AOF替换旧的AOF
这样就可以保证重写日志期间的所有操作也都会写入新的AOF文件。
需要注意的是, T7 T8执行的任务会阻塞服务器处理命令。
总的来说,就是每次 AOF 重写时,Redis 会先fork出一个子进程用于重写;然后,使用两个日志保证在重写过程中,新写入的数据不会丢失。
AOF文件恢复在Redis 服务器重启后,会优先去载入AOF日志文件。因为AOF文件里面包含了重建数据库状态所需的所有写命令,所以服务器重新执行一遍AOF文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态。
而由于Redis命令只能在客户端上下文中执行,Redis会创建一个没有网络连接的伪客户端来执行AOF文件中的内容。
小结