主题
持久化实践:RDB、AOF 与混合持久化的取舍
本文是 Redis 系统学习系列的 L2 核心篇。前置:29. 数据结构拆解:SDS、Dict、Listpack 与 Skiplist、30. 内存模型与编码切换。 学完可以配合面试题食用:02-persistence-rdb-aof
为什么需要持久化
Redis 是内存数据库,数据全在内存里。没持久化时,掉电或重启就是一场灾难——缓存可重建,但缓存预热期间的流量冲击是实打实的。如果 Redis 还在存业务数据(会话、计数器、订单缓存),那损失更大。
持久化就是给内存数据上个"保险":重启后能从磁盘恢复,不用等流量慢慢填满缓存。
RDB:快照式全量备份
RDB 把内存数据全量 dump 成一份二进制文件,默认叫 dump.rdb。触发方式有两种:
- SAVE:主进程直接写盘,阻塞期间不响应任何命令(生产不要用)
- BGSAVE:fork 子进程写盘,主进程继续服务
BGSAVE 是生产主力。核心机制是 fork + COW(Copy-On-Write):
- 主进程 fork 出子进程,子进程瞬间看到父进程的完整内存页表
- 子进程开始写磁盘,主进程继续接受写入
- 主进程要修改某页内存时,先复制一页再改,保证子进程看到的是 fork 时刻的"冻结"数据
text
主进程 子进程
| |
|-- fork -----> 拥有 fork 时刻的内存快照
| |-- 写 dump.rdb(慢IO)
|-- 继续响应 |
|-- 修改页时 |
| COW 复制页 |自动触发靠 save 配置:
conf
save 900 1 # 900 秒内至少 1 次写入 -> 触发 BGSAVE
save 300 10 # 300 秒内至少 10 次写入
save 60 10000 # 60 秒内至少 10000 次写入任意一个条件满足就触发。RDB 的缺点:两次快照之间的数据会丢。设 save 60 10000,如果第 59 秒崩了,最后 10000 次写入全丢——这个窗口对数据敏感业务不可接受。
AOF:写后日志,增量持久化
AOF 的思想是记操作日志:每次写命令执行后,追加到 appendonly.aof 文件。重启时重放日志恢复数据。
三个关键设计:
写后日志:命令先执行成功,再写 AOF。好处是只记录合法命令,坏处是执行成功但写日志前崩了——这条命令就丢了。这跟 MySQL 的 WAL(先写日志再改数据)反过来。
fsync 策略:write() 只写到内核缓冲区,掉电还是丢。fsync() 才刷盘。三种策略:
appendfsync always # 每次写命令都 fsync,丢失一个命令,但写性能极差(几百 TPS)
appendfsync everysec # 每秒 fsync 一次,最多丢 1 秒数据,写性能约 1 万+ TPS
appendfsync no # 交给 OS 刷盘,最多丢 30 秒数据,性能最高生产选 everysec——数据安全与性能的平衡点。
AOF 重写(BGREWRITEAOF):AOF 文件会一直膨胀。比如 SET k 1、SET k 2、SET k 3,重写后只保留 SET k 3。本质也是 fork 子进程,扫描内存生成最小 AOF 文件。
Redis 7.0 引入 Multi-Part AOF:将 AOF 拆成基础文件(base)和增量文件(incr),重写时只替换 base,incr 持续追加,切换更平滑。
混合持久化:兼顾恢复速度与数据完整性
aof-use-rdb-preamble yes 开启混合模式。AOF 重写时,子进程先写一份 RDB 格式的快照,再追加新写入的 AOF 命令。文件结构:
[ RDB 格式快照(全量数据、压缩) | AOF 增量命令 ]重启恢复时,先加载 RDB(快),再重放 AOF 增量(补全重写后的数据)。Redis 4.0 之后推荐默认开启,既解决了纯 RDB 的丢数据窗口,又解决了纯 AOF 重启慢的问题。
动手实操
实验:三种配置的丢失窗口对比
bash
# 1. 启动空 Redis
docker run --name redis-persist -d -p 6379:6379 redis:7-alpine
# 2. 写入 100 条数据
for i in $(seq 1 100); do
redis-cli SET k$i v$i
done
# 3. 检查当前持久化配置
redis-cli CONFIG GET save
redis-cli CONFIG GET appendfsync
# 4. 模拟纯 RDB 场景(关 AOF)
redis-cli CONFIG SET appendonly no
redis-cli CONFIG SET save "5 1" # 5秒内1次写入就触发
# 写入 10 条数据后立即 kill -9
sleep 2
redis-cli SET k_lost "i_am_lost"
kill -9 $(docker inspect redis-persist --format '{{.State.Pid}}')
# 重启后 count 确认丢失
docker start redis-persist
sleep 2
redis-cli DBSIZE # 可能只有 100(SET k_lost 丢了)生产配置模板
conf
# 开启混合持久化(推荐)
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
# RDB 作为兜底方案(保留一个 save 条件)
save 3600 1
# 重写控制
auto-aof-rewrite-percentage 100 # 文件增长 100% 触发重写
auto-aof-rewrite-min-size 64mb # 最小 64MB 才触发fork 停顿监控
bash
# 查看最近一次 fork 耗时
redis-cli INFO stats | grep fork_usec
# 大于 1 秒就要注意:大内存实例的 fork 耗时与内存成正比
# 经验值:10GB 实例 fork 约 300-500ms,30GB 约 1-2s常见误区与小结
- RDB 关了就安全? —— 纯 AOF 的 everysec 仍然有 1 秒丢失窗口。极端耐久应 always,但记住 TPS 会掉到几百。
- AOF 文件越大越慢? —— 加载时确实,但 7.0 的 Multi-Part 和重写机制让运行期影响可控。
- 混合持久化是万能的? —— 恢复速度比纯 AOF 快,但 fork 停顿仍然存在。大内存实例(>30GB)的 fork 开销是硬伤,只能拆实例。
- 持久化等于数据不丢? —— 不等于。异步复制下主库崩了,AOF 也丢了最后 1 秒——主从 + 持久化组合才稳。
- 纯缓存场景也要开? —— 不需要。redis.io 官方说"缓存可以关持久化"——重启后缓存重建是预期行为。
小结:持久化是 Redis 的"安全网",不是"免死金牌"。RDB 快但丢数据,AOF 全但重启慢,混合持久化取了中间值。生产选型看两张表:
| 场景 | 方案 |
|---|---|
| 纯缓存 | 关持久化,或开 RDB 兜底 |
| 业务缓存(可接受 1 秒丢失) | AOF everysec + 混合持久化 |
| 极端耐久 | AOF always + NVMe |
| 大内存实例(>30GB) | 拆实例,避免单点 fork 停顿 |
下一篇:从主从到 Cluster,看 Redis 如何从一个单机缓存演进成分布式系统。
参考
参考:Redis 官方文档 Persistence、Redis 7.0 Multi-Part AOF 设计文档