Redis除了RDB持久化外,还提供了AOF持久化,AOF持久化通过保存Redis服务端执行的命令来记录数据库状态。
1. AOF文件
1 | // AOF内容格式 |
Redis服务端存储着AOF相关的一些信息:
1 | struct redisServer { |
2. AOF持久化
AOF持久化功能分为追加、写入文件、同步磁盘三个步骤。
2.1 命令追加
当Redis服务器AOF功能开启情况下,服务器执行完一条写命令之后,会将该条命令以AOF格式追加到服务器的AOF缓冲区中。
- 判断追加命令是否为当前选中数据库,如果不是,则追加SELECT指令
- 根据不同命令类型分别进行处理,生成AOF格式内容
- AOF开启状态,将追加到server.aof_buf服务器AOF缓冲区中
- 如果有AOF子进程正在进行,那么也追加到AOF重写缓冲区中
2.2 写入文件和同步磁盘
Redis服务器进程是一个事件循环,文件事件负责接收客户端的命令请求,以及回复客户端,而时间事件负责执行定时任务,服务器在每次结束事件循环之前,会调用flushAppendOnlyFile函数,判断是否将AOF缓冲区内容写入到AOF文件中。
AOF缓冲区内容写入到文件后是否进行AOF同步有三种策略:
aof_fsync选项 | 描述 |
---|---|
AOF_FSYNC_NO | 目前为未使用,表示不手动执行fsync,等待操作系统执行 |
AOF_FSYNC_ALWAYS | 立即同步到磁盘。 |
AOF_FSYNC_EVERYSEC | 每秒同步一次,该策略下如果后台后fsync执行,那么我们延迟flush操作,最多延迟2秒钟。因为linux上的write(2)操作会被后台的fsync阻塞。 |
为了提高文件写入效率,用于调用write函数写入文件时,会将数据写入到内存缓冲区,等待缓冲区被填满或手动执行同步时,才会将内容中同步到磁盘中。
3. AOF载入
AOF文件中保存了服务端执行的命令,所以只要执行这些命令,那么服务器的状态就可以被还原。
- 创建一个不带网络连接的伪客户端,因为Redis命令只能在客户端中执行
- 循环从AOF文件中读取并解析指令,并在伪客户端中执行
执行完AOF命令,服务器就还原到了之前的状态。
服务器在AOF载入阶段,会间歇性处理网络客户端发送的请求,能执行的只有PUBSUB等指令
4. AOF重写
AOF持久化是通过保存命令来记录数据库状态,但随着运行时间的延迟,指令的数量会越来越大,而AOF文件体积随着也会变得很大,所以我们通过AOF重写,来减小AOF文件的大小。AOF重写是检查当前数据状态,然后生成新的指令,写入到AOF文件,以这种方式来减少命令数量。
1 | # 客户端执行以下命令 |
如果按照普通AOF保存,那么需要保存4条指令,而读取数据库状态后生产新的则只需要一条指令。
AOF重写会进行大量的写操作,所以调用这个函数会阻塞当前线程,而Redis使用单线程处理命令请求,所以AOF重写任务由创建新的子进程执行,这样子进程AOF重写期间,父进程仍然可以处理客户端请求。但这样也会存在一个问题,子进程执行AOF重写期间,父进程会处理请求,这会导致数据库不一致情况发生。
为了解决这种不一致,Redis服务器设置了一个AOF重写缓冲区,当redis服务器执行完命令后,会将这条命令写入AOF缓冲区和AOF重写缓冲区。当子进程AOF重写完成后,向父进程发送一个信号,父进程接收到该信号会调用信号处理函数,将AOF重写缓冲区中的所有内容写入到子进程处理的AOF文件中,这时新AOF文件保存的数据库状态与当前服务器一致。
用户调用BGREWRITEAOF
Redis调用rewriteAppendOnlyFileBackground()函数fork子进程
a. 子进程在临时文件中进行AOF重写
b. 父进程累积计算差异,追加到AOF重写缓冲区server.aof_rewrite_buf
2a完成后,子进程退出
父进程捕捉子进程信号,将AOF重新缓冲区中记录的差异追加到临时文件,临时文件改名替换真正的AOF文件。
5. 源码剖析
- 加载AOF文件
1 | int loadAppendOnlyFile(char *filename) { |
- AOF指令追加
1 | void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) { |
- 同步AOF到文件
1 | void flushAppendOnlyFile(int force) { |
- AOF后台重写
1 | int rewriteAppendOnlyFileBackground(void) { |