第 11 章 AOF 持久化
AOF(Append Only File)持久化,通过保存服务器所执行的写命令来记录数据库状态
被写入 AOF 文件的所有命令都是以 Redis 命令请求协议保存的,即纯文本格式
11.1 AOF 持久化的实现
三个步骤:
- 命令追加
- 文件写入
- 文件同步
11.1.1 命令追加
AOF 打开时,服务器在执行完写命令后以协议格式将写命令追加到服务器状态的 aof_buf 缓冲区末尾
11.1.2 AOF 文件的写入与同步
Redis 服务器进程是一个事件循环,循环中的文件事件负责接收客户端的命令请求、向客户端发送命令回复;时间事件负责执行类似 sercerCron 的定时运行函数
服务器每次结束一个事件循环之前,会调用 flushAppendOnlyFile 函数,考虑是否需要将 aof_buf 缓冲区中的内容写入和保存到 AOF 文件中
flushAppendOnlyFile 函数行为由配置的 appendfsync 值决定
默认为 everysec
文件的写入和同步
为了提高写入效率,用户调用 write 函数,将数据写入到文件时,操作系统通常会将写入的数据暂时保存在一个内存缓冲区中,等缓冲区填满或者超过指定时限后,才将缓冲区数据写入到磁盘中
但如果缓冲区数据还未写入前计算机发生故障,缓冲区数据将会丢失
系统提供 fsync 和 fdatasync 函数,强制让操作系统将缓冲区数据立即写入
AOF 持久化效率和安全性
- always:效率最低,安全性最高
- everysec:足够快,故障停机也只丢失一秒钟的命令数据
- no:速度最快,但单次同步时间最长,平摊角度和 everysec 类似,但故障停机后会丢失自上次 AOF 后的所有命令数据
11.2 AOF 文件的载入与数据还原
读入文件并重新执行一遍写命令就可以还原服务器关闭之前的数据库状态
详细步骤:
- 创建一个不带网络连接的伪客户端 fake client
- 因为 Redis 命令只能在客户端上下文执行,而还原来自 AOF 文件而不是网络连接,所以需要一个不带网络连接的伪客户端,而执行效果完全一样
- 从 AOF 文件分析并读取出一条写命令
- 使用伪客户端执行被读出的写命令
- 直到 AOF 文件所有写命令处理完毕
11.3 AOF 重写
随着服务器运行,AOF 文件内容越来越多,体积越来越大,还原的时间越多,于是就有了 AOF 重写功能
AOF 文件重写功能:创建新的 AOF 文件来代替现有的 AOF 文件,数据库状态相同,但不会包含任何浪费空间的冗余命令,于是体积会比较小
11.3.1 AOF 文件重写的实现
实际上 AOF 文件重写并不需要对现有的 AOF 文件进入任何读取、分析或者写入操作,而是通过读取服务器当前的数据库状态来实现的
假设对一个键值对进行了非常多的操作,重写只需要找到最终的状态,并用一条命令去实现这个状态即可
127.0.0.1:6379> rpush list "A" "B"
(integer) 2
127.0.0.1:6379> rpush list "C"
(integer) 3
127.0.0.1:6379> rpush list "D" "E"
(integer) 5
127.0.0.1:6379> lpop list
"A"
127.0.0.1:6379> lpop list
"B"
127.0.0.1:6379> rpush list "F" "G"
(integer) 5
127.0.0.1:6379> lrange list 0 4
1) "C"
2) "D"
3) "E"
4) "F"
5) "G"
以上命令可精简为
127.0.0.1:6379> rpush list "C" "D" "E" "F" "G"
(integer) 5
首先从数据库读取键现在的值,用一条命令去记录键值对,代替之前记录这个键值对的多条命令
伪代码如下:
实际中,为了避免命令时造成客户端输入缓冲区溢出,重写程序在处理列表、哈希表、集合、有序集合这四种可能带有多个元素的键时,会检查元素数量,如果超过了 redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD (默认为64)常量的值,那么重写程序将使用多条命令来记录键的值
11.3.2 AOF 后台重写
aof_rewrite 会进行大量的写入,调用这个函数的线程会被堵塞,如果是单线程工作的话将无法处理客户端发来的命令请求
于是 AOF 重写在子程序里运行:
- 子进程 AOF 重写期间,服务器父进程可以继续处理命令请求
- 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以避免在使用锁的情况下保证数据安全性
服务器父进程仍在接收客户端命令,可能有命令造成数据库状态修改,使得当前的数据库状态和重写之后的 AOF 文件保存的数据库状态不一致
解决:
Redis 设置了一个 AOF 重写缓冲区,在服务器创建子进程后开始使用,当服务器执行完写命令后,同时将写命令发给 AOF 缓冲区和 AOF 重写缓冲区
在子进程执行 AOF 重写时,服务器进程有以下三个工作:
- 执行客户端发来的命令
- 将执行后的写命令追加到 AOF 缓冲区
- 将执行后的写命令追加到 AOF 重写缓冲区
保证:
- AOF 缓冲区内容定期写入和同步 AOF 文件
- 子进程创建开始,服务器的所有写命令会被记录在 AOF 重写缓冲区
子进程完成 AOF 重写后向父进程发送信号,父进程接收后调用函数执行工作:
-
将 AOF 重写缓冲区内容写入新的 AOF 文件
-
AOF 文件改名,原子地覆盖现有的 AOF 文件
-
此期间阻塞父进程