引言
Redis 作为内存数据库,虽然读写速度非常快,但也正因为数据主要存放在内存中,一旦进程退出、机器宕机或者服务异常重启,如果没有持久化机制,数据就可能全部丢失。
因此,持久化是 Redis 保障数据可靠性的关键能力。
Redis 主要提供两类持久化方式:
- AOF(Append Only File)
- RDB(Redis DataBase Snapshot)
前者偏向记录操作过程,后者偏向记录某一时刻的数据结果。Redis 4.0 之后又进一步提供了混合持久化方案,用于在性能和数据安全之间做更好的平衡。
本文就从这三部分出发,梳理 Redis 持久化的核心机制和适用思路。
什么是 AOF 日志
AOF 的全称是 Append Only File。它的核心思路是:
- 把 Redis 收到的写命令,以追加的方式记录到日志文件中
当 Redis 重启恢复时,就可以重新执行这些命令,从而恢复数据。
AOF 日志格式
AOF 中记录的是 Redis 执行过的写命令,这些命令会以协议相关的文本形式写入文件。
例如一次 SET、一次 HSET、一次 INCR,都会被追加到 AOF 文件中。
所以可以把 AOF 理解成:
- 一份"命令流水账"
它记录的不是某个时间点的全量数据,而是"为了变成现在这个状态,Redis 都执行过哪些命令"。
AOF 日志写入时机
AOF 和很多数据库日志的设计思路不完全一样。
Redis AOF 采用的是:
- 写后日志
也就是:
- 先执行命令
- 再把命令写入 AOF 日志
为什么采用写后日志
这么设计有几个明显好处:
- 只有真正执行成功的命令才会被记录
- 不会把错误命令写进日志
- 可以减少额外校验和回滚处理的复杂度
但它也带来一个问题:
- 如果命令执行成功了,但日志还没来得及落盘,Redis 就宕机了,那么这条命令就会丢失
所以 AOF 的可靠性,很大程度上取决于写回策略。
AOF 的三种写回策略
Redis 提供了三种典型的 AOF 写回策略,用于在性能和数据安全之间做取舍。
Always:每条命令都立即写回
Always 的意思是:
- 每执行完一条写命令,就立刻同步把日志写回磁盘
它的优点是:
- 数据最安全
- 宕机时最多只会丢失极小范围内的操作
缺点也很明显:
- 每条命令都要触发磁盘同步
- 对性能影响最大
所以它更适合数据可靠性要求极高、但吞吐压力不算特别大的场景。
Everysec:每秒写回一次
Everysec 是 Redis AOF 的默认常见策略。
它的方式是:
- 写命令执行完后,先把日志写入内存缓冲区
- 每隔一秒把缓冲区内容写回磁盘
它的优点是:
- 性能和安全性比较平衡
- 对大多数业务来说是一个比较稳妥的选择
它的代价是:
- 理论上可能丢失 1 秒左右的数据
No:由操作系统决定何时写回
No 的意思是:
- Redis 只负责把日志写到 AOF 文件对应的内存缓冲区
- 具体什么时候刷盘,交给操作系统决定
这种策略的优点是:
- 性能最好
缺点是:
- 可控性最差
- 一旦宕机,可能丢失的数据会更多
因此工程上更常用的通常还是 Everysec。
为什么需要 AOF 重写
AOF 有一个天然问题:
- 文件会越来越大
因为 AOF 是通过追加命令记录变化的,只要有写请求,文件就会持续增长。
如果一个键被反复修改很多次,AOF 中就会保留很多条历史命令。例如:
text
SET k 1
SET k 2
SET k 3
SET k 4
但恢复数据时,真正有意义的其实只是最后一次结果。
这就意味着:
- AOF 文件会不断膨胀
- 恢复时间也会越来越长
所以 Redis 引入了 AOF 重写机制。
AOF 重写是怎么做的
AOF 重写的核心思想是:
- 不再基于旧 AOF 文件逐条改写
- 而是基于当前内存中的最新数据状态,重新生成一份更精简的新 AOF 文件
这样一来,一个键值对最终只需要保留能够表达"当前状态"的命令,而不需要保留所有历史修改过程。
例如某个键最终值是 4,那么重写后只需要一条:
text
SET k 4
而不需要保留前面的 SET k 1、SET k 2、SET k 3。
AOF 重写过程怎么理解
AOF 重写过程常被概括成:
- 一个拷贝
- 两处日志
一个拷贝
当 Redis 执行 AOF 重写时,主线程会通过 fork 创建一个后台子进程,比如 bgrewriteaof。
这个子进程会基于当前内存数据,生成新的 AOF 文件。
这里要注意一点:
fork并不会立刻把整块物理内存完整复制一份
子进程复制的主要是父进程的页表,也就是虚拟内存到物理内存的映射关系。父子进程在初始阶段会共享同一份物理内存,只有后续发生写入时,才会通过写时复制机制分离。
所以更准确地说,这里不是"立刻完整拷贝所有内存数据",而是:
- 先共享内存映射
- 需要修改时再复制
两处日志
在 AOF 重写期间,Redis 主线程并不会停止处理新的写请求。
这时新写操作会同时落到两个地方:
第一处:原有 AOF 缓冲区
新的写命令仍然会写入当前正在使用的 AOF 缓冲区。
这样即使在重写过程中发生宕机,原来的 AOF 仍然是完整可用的。
第二处:AOF 重写缓冲区
新的写命令也会写入 AOF 重写缓冲区。
等后台子进程完成新 AOF 文件重写后,这部分"重写期间产生的新命令"会被追加到新的 AOF 文件末尾,确保新文件也能反映数据库最新状态。
所以整个过程的关键就是:
- 一边生成新 AOF
- 一边不丢失重写期间的新写入
什么是 RDB 快照
和 AOF 不同,RDB 记录的不是命令过程,而是:
- 某一时刻 Redis 的全量数据快照
RDB 文件是二进制格式,因此恢复时可以直接把快照内容读入内存,而不需要逐条重放命令。
这也是为什么:
- RDB 恢复速度通常比 AOF 更快
生成 RDB 时会阻塞主线程吗
Redis 提供了两个命令生成 RDB 文件:
savebgsave
save
save 会在主线程中直接执行快照生成,因此会阻塞 Redis 主线程。
这意味着:
- 在生成 RDB 文件期间,Redis 无法正常处理其他请求
所以线上生产环境一般不推荐直接使用它。
bgsave
bgsave 会创建一个后台子进程来生成 RDB 文件,从而避免主线程长时间阻塞。
这也是 Redis 中更常见的快照方式。
所以如果希望尽量不影响业务请求,通常应当使用:
bgsave
生成 RDB 时,数据还能被修改吗
可以。
Redis 在执行 bgsave 时,会借助操作系统提供的:
- 写时复制(Copy-On-Write,简称 COW)
来保证快照和在线写请求可以同时进行。
写时复制怎么理解
当主线程 fork 出 bgsave 子进程后,父子进程初始时共享同一份物理内存页面。
如果主线程只读数据
如果主线程只是读取某块数据,那么它和 bgsave 子进程可以继续共享同一份内存,彼此没有影响。
如果主线程修改数据
如果主线程要修改某块数据,那么操作系统会把这块数据复制一份出来。
之后:
- 主线程修改新的副本
bgsave子进程继续读取旧数据
这样就保证了:
- RDB 快照写入的是某一时刻的稳定数据
- 主线程也能继续处理新的写请求
这就是 COW 在 Redis 快照中的核心作用。
Redis 持久化的最佳方案怎么选
如果只用 RDB:
- 恢复速度快
- 文件紧凑
- 但两次快照之间如果发生宕机,可能丢失较多数据
如果只用 AOF:
- 数据安全性通常更高
- 可控性更强
- 但文件更大,恢复过程也可能更慢
所以 Redis 4.0 提出了混合持久化方案。
什么是混合持久化
混合持久化的核心思路是:
- 以 RDB 快照作为基础
- 在两次快照之间,用 AOF 记录增量命令
也就是说:
- 定期做一次内存快照
- 快照之后的写操作,再通过 AOF 记录
这样做的好处是:
- 不需要频繁执行全量快照,减少
fork带来的影响 - AOF 只需要记录两次快照之间的操作,文件不会无限膨胀得太快
- 恢复时可以先加载快照,再重放后续少量 AOF 命令,兼顾恢复速度和数据安全
为什么混合持久化更平衡
混合持久化本质上是在吸收两种方案的优点:
- 用 RDB 解决全量恢复效率问题
- 用 AOF 解决快照间隔导致的数据丢失问题
所以如果从性能、恢复效率和数据安全这三个维度综合考虑,混合持久化通常是更均衡的方案。
总结
这篇文章可以压缩成几条核心结论:
- AOF 记录的是写命令,采用写后日志机制
- AOF 有
Always、Everysec、No三种写回策略,其中Everysec是常见平衡方案 - AOF 重写不是基于旧日志裁剪,而是基于当前内存状态重建精简日志
- RDB 记录的是某一时刻的全量快照,恢复速度通常更快
save会阻塞主线程,bgsave则通过后台子进程减少阻塞- RDB 在生成快照时依赖写时复制技术,保证主线程仍可继续处理写请求
- Redis 4.0 的混合持久化方案,能够更好地平衡性能、恢复速度和数据安全
如果把 Redis 持久化的目标概括成一句话,那就是:
"尽量少影响在线性能,同时尽量少丢数据,并尽量提高恢复效率。"
如果这篇文章对你有帮助,欢迎继续阅读本系列后续内容。若文中有不准确或需要补充的地方,也欢迎指出。