在运行情况下,Redis 以数据结构的形式将数据维持在内存中,为了让这些数据在 Redis 重启之后仍然可用,需要将数据写入持久存储
持久化是指将数据写入持久存储,例如固态磁盘(SSD)
Redis 提供了一系列持久化选项。这些包括:
RDB(Redis Database)
:将数据库的快照(snapshot)以二进制的方式保存到磁盘中AOF(Append Only File)
:以协议文本的方式,将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件,以此达到记录数据库状态的目的RDB + AOF
:RDB和AOF混合方式(4.0版本)
RDB详解见Redis持久化——RDB机制详解
AOF
本文首先介绍 AOF 功能的运作机制,了解命令是如何被保存到 AOF 文件里的,观察不同的 AOF 保存模式对数据的安全性、以及 Redis 性能的影响。
之后会介绍从 AOF 文件中恢复数据库状态的方法,以及该方法背后的实现机制。
最后还会介绍对 AOF 进行重写以调整文件体积的方法,并研究这种方法是如何在不改变数据库状态的前提下进行的
AOF文件使用网络通讯协议的格式来保存这些命令
AOF例子
若执行以下命令:
redis> RPUSH list 1 2 3 4
(integer) 4
redis> LRANGE list 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
redis> KEYS *
1) "list"
redis> RPOP list
"4"
那么其中两条对数据库有修改的写入命令就会被同步到 AOF 文件中:
RPUSH list 1 2 3 4
RPOP list
上面列举的两个命令在 AOF 文件实际保存如下:
*2
$6
SELECT
$1
0
*6
$5
RPUSH
$4
list
$1
1
$1
2
$1
3
$1
4
*2
$4
RPOP
$4
list
除了 SELECT 命令是 AOF 程序自己加上去的之外, 其他命令都是之前我们在终端里执行的命令
*
表示后面语句的词个数,$
表示后面词的字节数
AOF同步步骤
同步命令到 AOF 文件的整个过程可以分为三个阶段:
- 命令传播: Redis 将执行完的命令、命令的参数、命令的参数个数等信息发送到 AOF 程序中
- 缓存追加: AOF 程序根据接收到的命令数据,将命令转换为网络通讯协议的格式,然后将协议内容追加到服务器的 AOF 缓存中
- 文件写入和保存: AOF 缓存中的内容被写入到 AOF 文件末尾,如果设定的 AOF 保存条件被满足的话,
fsync
函数或者fdatasync
函数会被调用,将写入的内容真正地保存到磁盘中
命令传播
当一个 Redis 客户端需要执行命令时,它通过网络连接,将协议文本发送给 Redis 服务器
比如说,要执行命令SET KEY VALUE
,客户端将向服务器发送文本*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n
服务器在接到客户端的请求之后,它会根据协议文本的内容,选择适当的命令函数,并将各个参数从字符串文本转换为 Redis 字符串对象(StringObject)
每当命令函数成功执行之后, 命令参数都会被传播到 AOF 程序, 以及 REPLICATION 程序
缓存追加
当命令被传播到 AOF 程序之后,程序会根据命令以及命令的参数,将命令从字符串对象转换回原来的协议文本
缓存追加过程可以分为以下三步:
- 接受命令、命令的参数、以及参数的个数、所使用的数据库等信息
- 将命令还原成 Redis 网络通讯协议
- 将协议文本追加到
aof_buf
末尾
协议文本生成之后,它会被追加到redis.h/redisServer
结构的aof_buf
末尾
redisServer
结构维持着 Redis 服务器的状态,aof_buf
域则保存着所有等待写入到 AOF 文件的协议文本
struct redisServer {
// 其他域...
sds aof_buf;
// 其他域...
};
文件写入和保存
每当服务器常规任务函数被执行、或者事件处理器被执行时,aof.c/flushAppendOnlyFile
函数都会被调用,这个函数执行以下两个工作:
WRITE
:根据条件,将aof_buf
中的缓存写入到 AOF 文件SAVE
:根据条件,调用fsync
或fdatasync
函数,将 AOF 文件保存到磁盘中
两个步骤都需要根据一定的条件来执行 而这些条件由 AOF 所使用的保存模式来决定。以下会介绍 AOF 所使用的三种保存模式,以及在这些模式下,步骤WRITE和SAVE的调用条件
AOF 保存模式
Redis 目前支持三种 AOF 保存模式
Always
,同步写回:每个写命令执行完,立马同步地将日志写回磁盘Everysec
,每秒写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘No
,操作系统控制的写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
AOF重写
根据键的类型,使用适当的写入命令来重现键的当前值,这就是 AOF 重写的实现原理
所谓的“重写”其实是一个有歧义的词语,实际上 AOF 重写并不需要对原有的 AOF 文件进行任何写入和读取,它针对的是数据库中键的当前值
AOF后台重写
AOF 重写是一个有歧义的名字,实际的重写工作是针对数据库的当前值来进行的,程序既不读写、也不使用原有的 AOF 文件
AOF 后台重写,是为了避免主进程被阻塞,无法处理请求,所以采用主进程fork
出子进程,用于 AOF 重写。为了避免在 AOF 重写期间新命令对现有数据的修改导致的不一致问题,Redis 增加了一个AOF 重写缓存,这个缓存在fork
出子进程之后开始启用,Redis 主进程在接到新的写命令之后,除了会将这个写命令的协议内容追加到现有的 AOF 文件之外,还会追加到这个缓存中
换言之, 当子进程在执行 AOF 重写时, 主进程需要执行以下三个工作:
- 处理命令请求
- 将写命令追加到现有的 AOF 文件中
- 将写命令追加到 AOF 重写缓存中
当子进程完成 AOF 重写之后,它会向主进程发送一个完成信号,主进程在接到完成信号之后,会调用一个信号处理函数,并完成以下工作:
- 将 AOF 重写缓存中的内容全部写入到新 AOF 文件中。
- 对新的 AOF 文件进行改名,覆盖原有的 AOF 文件
以上就是 AOF 后台重写, 也即是BGREWRITEAOF命令的工作原理
AOF优缺点
- 优点
- 拥有不同的
fsync
策略,fsync
是使用后台线程执行的,写入性能很好 - AOF是一个仅追加日志,没有查找和断电时的损坏问题。即使由于某种原因(磁盘已满或其他原因)日志以写入一半的命令结束,
redis-check-aof
工具也能够轻松修复它 - 当 AOF 变得太大时,Redis 能够在后台自动重写 AOF
- AOF 以易于理解和解析的格式包含一个接一个地记录所有操作的日志,使得导出和恢复十分简单
- 拥有不同的
- 缺点
-
对于相同的数据集,AOF 文件通常比等效的 RDB 文件大
-
根据确切的 fsync 策略,AOF 可能比 RDB 慢
Redis < 7.0
-
如果在重写期间有对数据库的写入,AOF 会使用大量内存
-
重写期间到达的所有写入命令都会写入磁盘两次
-
Redis 可能会在重写结束时冻结写入并将这些写入命令同步到新的 AOF 文件
-
RDB和AOF混合方式(4.0版本)
Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。
这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。
如下图所示,T1 和 T2 时刻的修改,用 AOF 日志记录,等到第二次做全量快照时,就可以清空 AOF 日志,因为此时的修改都已经记录到快照中了,恢复时就不再用日志了。
这个方法既能享受到 RDB 文件快速恢复的好处,又能享受到 AOF 只记录操作命令的简单优势, 实际环境中用的很多。
参考资料:
- Redis persistence
- Redis 设计与实现
- Redis持久化详解