因为 Redis 是内存数据库,它将自己的数据库状态存储在内存里面,所以如果不把储存在内存中的数据保存到磁盘上的话,那么一旦服务器进程退出,服务器中的数据也会消失不见。为了把数据保存到磁盘上,Redis 提供了两种持久化的功能,避免了数据意外丢失。
1. RDB 持久化
RDB 方式的持久化是通过快照来完成的,当符合一定条件时,Redis 会自动将内存中的所有数据生成一个副本并存储在硬盘上,这个过程即为“快照”。Redis 默认会将快照文件存储在 Redis 当前进程的工作目录中的 dump.rdb 文件中,可以通过配置 dir 和 dbfilename 参数分别制定快照文件的存储路径和文件名。
RDB 持久化既可以手动执行,也可以根据服务器配置选项定期执行,该功能将数据库状态保存到一个 RDB 文件中。RDB 文件是一个压缩过的二进制文件,通过该文件,可以还原生成 RDB 文件时的数据库状态。在启动 Redis 服务器的时候,Redis 会打印启动日志,其中的 "DB loaded from disk: 0.035 seconds" 日志信息就是在服务器成功载入 RDB 文件后打印的。
Redis 有两个命令可以生成 RDB 文件,一个是 SAVE,另一个是 BGSAVE。SAVE 命令会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止;而 BGSAVE 命令则会派生出一个子进程,然后由子进程负责创建 RDB 文件,服务器进程不会被阻塞。
在 Redis 的配置文件中,我们可以找到类似如下的信息:
save 900 1
save 300 10
save 60 10000
这表示了只要满足以下三个条件之一,BGSAVE 命令就会被执行:
- 服务器在 900 秒内,对数据库进行了至少 1 次修改
- 服务器在 300 秒内,对数据库进行了至少 10 次修改
- 服务器在 60 秒内,对数据库进行了至少 10000 次修改
2. AOF 持久化
与 RDB 持久化通过保存数据库中的键值对来记录数据库状态不同,AOF 持久化是通过保存 Redis 服务器所执行的写命令来记录数据库状态的。因为 AOF 文件里包含了重建数据库状态所需要的所有写命令,所以服务器只需要读入并重新执行一遍 AOF 文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态。
当 AOF 持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区中。服务器每次结束一个事件循环之前,都会调用 flushAppendOnlyFile 函数,考虑是否需要将 aof_buf 缓冲区中的内容写入和保存到 AOF 文件里。flushAppendOnlyFile 函数的行为由服务器配置的 appendfsync 选项的值(always、everysec、no)来决定。服务器配置的 appendfsync 选项的值决定了 AOF 持久化功能的效率和安全性:
- 当 appendfsync 的值为 always 时,服务器在每个事件循环都要将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,并且同步 AOF 文件,所以 always 的效率是最低的,但是安全性是最高的。因为即使出现故障停机,AOF 持久化也只会丢失一个事件循环中所产生的命令数据。
- 当 appendfsync 的值为 everysec 时,服务器在每个事件循环都要将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,并且每隔一秒就要在子线程中对 AOF 文件进行一次同步。从效率上来讲,everysec 模式足够快,并且就算出现故障停机,最多也只丢失一秒钟的命令数据。
- 当 appendfsync 的值为 no 时,服务器在每个时间循环都要将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,至于何时对 AOF 文件进行同步,则由操作系统来决定。
因为 AOF 文件的更新频率通常比 RDB 文件的更新频率高,所以如果服务器开启了 AOF 持久化功能,那么服务器会优先使用 AOF 文件来还原数据库状态;只有在 AOF 持久化功能处于关闭状态时,服务器才会使用 RDB 文件来还原数据库状态。
此外,为了避免 AOF 文件因为一些过期或冗余的命令导致文件体积过大,Redis 提供了对 AOF 文件进行重写的功能,重写功能会根据当前数据库的状态,记录下还原当前数据库状态所必须的命令,所以并不会造成空间的浪费。举个例子,如果服务器对 list 键执行了以下的命令:
redis > RPUSH list "A" "B" ["A", "B"]
(integer) 2
redis > RPUSH list "C" ["A", "B", "C"]
(integer) 3
redis > LPOP list ["B", "C"]
"A"
redis > RPUSH list "D" "E" {"B", "C", "D", "E"]
(integer) 4
那么服务器为了保存当前 list 键的状态,必须在 AOF 文件中写入 4 条命令。而重写功能会读取当前 list 键的状态,然后通过一条命令:
RPUSH list "B" "C" "D" "E"
来替代上面的 4 条命令,这样就将保存 list 键所需要的命令从 4 条减少为 1 条了。