AOF持久化
RDB持久化是通过保存数据库中的键值对来记录数据库状态的,而AOF则是通过保存redis服务器所执行的命令来完成记录的。
例如执行命令:
1 | SET msg "hello" |
那么RDB的持久化就是保存msg和numbers的键值对,而AOF则是保存SET和RPUSH的命令。
AOF持久化的实现
AOF的持久化功能分为命令追加、文件写入、文件同步三个步骤。
命令追加
打开AOF持久化功能后,服务器在执行完一个写命令后,会以redis的协议格式将被执行的命令写到服务器状态的缓冲区,则redisServer结构的aof_buf字段。在大量写请求情况下,利用缓冲区缓存一部分命令,尔后再根据某种策略写入磁盘,减少IO。
1 | struct redisServer { |
AOF文件的写入与同步
Redis的服务器进程中有一个事件循环,正如注释所说的,每次结束事件循环前都会调用flushAppendOnlyFile()函数,该函数则根据配置选项决定如何写入AOF文件。
1 | void beforeSleep(struct aeEventLoop *eventLoop) { |
- force:如果持久化策略为everysec,就有一定的可能延迟flush,因为后台进程可能还在进行fsync(),而如果force设成1,则无论什么情况都会进行写入。
另外由于在Linux中用户调用write函数时,操作系统会先将写入数据保存在一个内存缓冲区中,redis支持服务器配置appendfsync选项来定义上面的函数行为:
1 | /* Append only defines */ |
- AOF_FSYNC_ALWAYS:将aof_buf缓冲区的所有内容写入并同步到AOF文件;
- AOF_FSYNC_EVERYSEC:将aof_buf缓冲区的所有内容写入AOF文件,如果上次同步AOF文件的时间距离现在超过1秒,则再次进行同步;
- AOF_FSYNC_NO:写入文件但不同步;
AOF文件的载入与数据还原
由于AOF文件包含了重建数据库的所有写命令,因此只需要重读执行一遍,就可以恢复服务器状态了。其实现函数为loadAppendOnlyFile()
1 | int loadAppendOnlyFile(char *filename) { |
- 由代码可见,首先是创建一个不带网络连接,不做回应不受blocked的客户端,因为执行命令只能在客户端上下文执行;
- 从AOF文件中分析并读出写命令;
- 用伪客户端执行该命令,知道所有命令处理完毕;
AOF重写
为了解决AOF文件体积膨胀的问题,Redis提供了AOF文件重写的功能,即Redis服务器会创建一个新的AOF文件来替代现有的AOF文件,并去除任何浪费空间的冗余命令。
AOF文件重写的实现
事实上,AOF文件重写并不会对老的AOF文件进行任何读取、分析或者写入操作,而是通过直接读取当前数据库的状态实现的。aof的重写是通过函数rewriteAppendOnlyFileRio实现的
1 | int rewriteAppendOnlyFileRio(rio *aof) { |
另外,以写入集合键为例,可以看到为了避免在执行命令时导致客户端输入缓冲区溢出,重写快速链表、哈希表、集合和有序集合这种带有多个元素的key时,会先检查key包含的元素数量。如果超过了AOF_REWRITE_ITEMS_PER_CMD,则会使用多条命令进行重写。默认是64。
1 |
|
AOF后台重写
为了避免函数会阻塞服务器处理客户端的请求,Redis将AOF重写放到子进程中执行,同时为了避免在子进程执行AOF重写期间,由于服务器进程在处理新的请求,从而使得现有数据库状态发生改变,Redis设置了一个AOF重写缓冲区,在服务器创建完子进程后开始使用,当Redis执行完一个写命令之后,会同时将写命令发送到AOF缓冲区和AOF重写缓冲区。
1 | void bgrewriteaofCommand(client *c) { |
首先判断是否已经存在相关bgrewrite子进程,倘若有会在这些命令完成后执行。否则会fork出子进程。在子进程完成aof重写后,会发一个信号给父进程,父进程会调用backgroundRewriteDoneHandler()将aof重写缓冲区中的所有内容写入到新的aof文件中,然后进行原子性地覆盖旧的aof文件。重写缓冲区的内容是通过aofRewriteBufferWrite写入到新的aof文件中的。
1 | ssize_t aofRewriteBufferWrite(int fd) { |