Redis中除了提供了RDB持久化以外,还提供了AOF持久化 (append-only file)。RDB持久化是把键值对保存在RDB文件中,AOF是把服务器中执行的命令(SET, SADD, RPUSH等)保存在AOF文件中。要恢复时,服务器通过载入和执行AOF文件中保存的命令来还原服务器关闭时的数据库状态。与RDB文件相比,AOF文件一般比较大,而且恢复速度比较慢。但是好处是相对的,因为RDB是每隔一段时间持久化,如果在这期间发生了故障,会丢失很多数据。
AOF持久化的实现
AOF持久化分为命令的追加,文件的写入,文件的同步这三个步骤。
- 命令的追加:
服务器在执行完一个写命令时,会将被执行的写命令追加到服务器状态中的aof_buf缓冲区的末尾。
struct redisServer
{
...
sds aof_buf;
...}
- AOF文件的写入:
redis 的服务器进程是一个事件循环,这个循环中有文件事件(负责接收客户端的命令请求,以及向客户端发送命令回复),有时间事件(负责执行一些定时函数(如RDB中的serverCron函数))。每次个循环都要考虑是否执行一个flushAppendOnlyFile(), 以便能够将aof_buff缓冲区中的内容写入和保存到AOF文件中。
写函数是如下实现的(在aof.c文件中):
write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
它将server.aof_buf中的数据写到server.aof_fd这个文件描述符里。
这里要特别注意的是,写入并不代表就是立即写入aof这个文件中。这里有两个概念,内核缓冲区和进程缓冲区。
aof_buf是服务器进程缓冲区的数据,调用write()函数时,是将进程缓冲区的数据写入内核缓冲区中,此时write()函数就返回了,而内核缓冲区的数据什么时候写入磁盘,需要的内核来说了算,通常内核会把要写的数据暂时存在缓冲区中,积累到一定数量后再一 次写入。有时会导致意外情况,比如断电,内核还来不及把内核缓冲区中的数据写道磁盘上,这些更新的数据就会丢失。
现代操作系统这样做是有道理的:因为这样能提高了磁盘的I/O效率(读写磁盘总是十分耗时的)。
- AOF的文件同步
所以flushAppendOnlyFIle()函数还有个同步的操作,其实是调用了fsync()或者fdatasync()这样的同步函数来将内核缓冲区的数据立即“冲洗”到磁盘上。
AOF文件的载入和数据还原
服务器读入AOF文件并重新执行一遍AOF文件里保存的写命令,就可以还原服务器关闭前的数据库状态。这里要注意的是还原时创建的一个不带连接的伪客户端。
AOF文件的重写
随着服务器运行时间的增加,AOF文件中的内容会越来越多,文件体积会越来越大,所以需要提供一个文件重写功能,用一个新的文件,这个文件不包含任何浪费空间的冗余指令,来代替原来的旧文件。这个新文件的体积会小很多。
Redis服务器进程在AOF文件重写时,fork()一个子进程,子进程拥有服务器进程的所有副本,用它来执行文件重写。这样服务器进程还能继续处理其他到来的命令。但是这样会产生一个问题,在子进程执行重写的过程中,服务器也可能执行写文件的操作,这样使得服务器当前数据库状态和AOF文件保存的数据库状态不一致。
为了解决这个问题,在子进程执行重写时,Redis又开启了一个AOF重写缓冲区,将执行的写命令存在这个缓冲区里,当子进程执行完之后,给父进程发送一个信号,父进程又将AOF重写缓冲区的命令追加到新的AOF文件中。
最后用新的AOF文件代替旧的AOF文件。重新工作完成。也就是BGREWRITEAOF命令的过程。