AOF 重写
AOF 持久化是通过保存写命令来记录数据变化的,随着时间的流逝 AOF 文件会越来越大,Redis 提供了 AOF 重写功能来解决 AOF 文件体积膨胀问题:创建一个新的 AOF 文件来替代现有的 AOF 文件,新的 AOF 文件不会包含任何浪费空间的冗余命令,体积大小相比旧的 AOF 文件小很多
实现原理
-
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 -1 1) "C" 2) "D" 3) "E" 4) "F" 5) "G"
-
当前列表键 list 的数据为
["C", "D", "E", "F", "G"]
要使用尽量少的命令来记录 list 键的状态,最简单的方式不是去读取和分析现有 AOF 文件的内容,而是直接读取 list 键在数据库中的当前值,然后用一条命令代替前面所有的命令:
RPUSH list "C" "D" "E" "F" "G"
重写步骤
数据库中读取键当前的值,然后用一条命令去记录键值对,代替之前记录该键值对的多个命令
- 遍历所有键
- 如果键带有过期时间,并且已经过期,则跳过
- 用
SET key value
命令来保存字符串键 - 用
RPUSH key item1 item2 ... itemN
命令来保存列表键 - 用
SADD key member1 member2 ... memberN
命令来保存集合键 - 用
HMSET key field1 value1 field2 value2 ... fieldN valueN
命令来保存哈希键 - 用
ZADD key score1 member1 score2 member2 ... scoreN memberN
命令来保存有序集键 - 如果 key 带有过期时间,则用
EXPIREAT key time
命令来保存键的过期时间
后台重写
- AOF 重写会进行大量的写入操作,调用这个重写的线程将被长时间的阻塞,因为 Redis 使用单线程来处理命令请求,所以如果直接是服务器进程进行重写的话,重写 AOF 期间服务器将无法处理客户端发送来的命令请求
- Redis 将 AOF 重写程序放到子进程里执行,有以下好处:
- 子进程进行 AOF 重写期间,主进程可以继续处理命令请求
- 子进程带有主进程的数据副本,使用子进程而不是线程,可以避免在锁的情况下,保证数据的安全性
出现的问题
子进程在进行 AOF 重写期间,服务器进程还要继续处理命令请求,而新的命令可能对现有的数据进行修改,这会让当前数据库的数据和重写后的 AOF 文件中的数据不一致
解决
- Redis 增加了一个 AOF 重写缓存,这个缓存在 fork 出子进程之后开始启用,Redis 服务器主进程在执行完写命令之后,会同时将这个写命令追加到 AOF 缓冲区和 AOF 重写缓冲区
- 子进程在执行 AOF 重写时,主进程需要执行以下三个工作:
- 执行 client 发来的命令请求
- 将写命令追加到现有的 AOF 文件中
- 将写命令追加到 AOF 重写缓存中
效果
- AOF 缓冲区的内容会定期被写入和同步到 AOF 文件中,对现有的 AOF 文件的处理工作会正常进行
- 从 fork 子进程开始,服务器执行的所有写操作都会被记录到 AOF 重写缓冲区中
完成重写后
-
当子进程完成对 AOF 文件重写之后,它会向父进程发送一个完成信号,父进程接到该完成信号之后,会调用一个信号处理函数,该函数完成以下工作:
-
将 AOF 重写缓存中的内容全部写入到新的 AOF 文件中,保证新 AOF 文件的数据和服务器当前的数据一致
-
对新的 AOF 文件进行改名,覆盖原有的 AOF 文件
-
-
当这个信号处理函数执行完毕之后,主进程就可以继续像往常一样接收命令请求了。在整个 AOF 后台重写过程中,只有最后的主进程写入命令到 AOF 缓存和对新的 AOF 文件进行改名及覆盖原有 AOF 文件这两个步骤会造成主进程阻塞,在其他时候,AOF 后台重写都不会对主进程造成阻塞,这将 AOF 重写对性能造成的影响降到最低
触发重写
AOF 重写可以由用户手动触发,也可以由服务器自动触发
手动
通过调用BGREWRITEAOF
手动触发
自动
服务器在 AOF 功能开启的情况下,会维持以下三个变量:
- 记录当前 AOF 文件大小的变量
aof_current_size
- 记录最后一次 AOF 重写之后,AOF 文件大小的变量
aof_rewrite_base_size
- 增长百分比变量
aof_rewrite_perc
每当服务器周期性操作函数serverCron
函数执行时,它会检查以下条件是否全部满足,如果全部满足的话,就触发自动的 AOF 重写操作:
- 没有 Redis 持久化正在执行
- 没有 AOF 重写正在进行
- 当前 AOF 文件大小要大于
server.aof_rewrite_min_size
(默认为1MB) - 当前 AOF 文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比
auto-aof-rewrite-percentage
(默认为100%)