Skip to content

持久化实践:RDB、AOF 与混合持久化的取舍

本文是 Redis 系统学习系列的 L2 核心篇。前置:29. 数据结构拆解:SDS、Dict、Listpack 与 Skiplist30. 内存模型与编码切换。 学完可以配合面试题食用:02-persistence-rdb-aof

为什么需要持久化

Redis 是内存数据库,数据全在内存里。没持久化时,掉电或重启就是一场灾难——缓存可重建,但缓存预热期间的流量冲击是实打实的。如果 Redis 还在存业务数据(会话、计数器、订单缓存),那损失更大。

持久化就是给内存数据上个"保险":重启后能从磁盘恢复,不用等流量慢慢填满缓存。

RDB:快照式全量备份

RDB 把内存数据全量 dump 成一份二进制文件,默认叫 dump.rdb。触发方式有两种:

  • SAVE:主进程直接写盘,阻塞期间不响应任何命令(生产不要用)
  • BGSAVE:fork 子进程写盘,主进程继续服务

BGSAVE 是生产主力。核心机制是 fork + COW(Copy-On-Write)

  1. 主进程 fork 出子进程,子进程瞬间看到父进程的完整内存页表
  2. 子进程开始写磁盘,主进程继续接受写入
  3. 主进程要修改某页内存时,先复制一页再改,保证子进程看到的是 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 1SET k 2SET 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 设计文档

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。
粤ICP备2026104257号-1