第六章 持久化:数据不丢失的秘密
前几章我们学会了用 Redis 存数据、查数据。但有一个现实问题:Redis 把数据放在内存里,进程一停、机器一重启,内存里的东西就没了。这一章讲 持久化(Persistence):怎样把内存里的数据安全地落到磁盘上,重启后还能找回来。
6.1 为什么需要持久化
Redis 是内存数据库,重启数据就没了
应用写入 Redis
|
v
+------------------+
| 内存 (RAM) | <-- 数据在这里,速度快
+------------------+
|
| 若未做持久化:进程退出 / 断电 / 重启
v
+------------------+
| 数据全部丢失 |
+------------------+- 内存读写快,但易失:断电、崩溃、正常关机后,未保存到磁盘的数据都会消失。
- 很多场景(缓存可以丢、主数据在别处)可以接受丢一部分;但若 Redis 里放的是会话、计数、队列等不能丢的数据,就必须做持久化。
持久化 = 把内存数据保存到磁盘
持久化的含义可以概括成一句话:
在适当时机,把内存中的数据以某种格式写入磁盘文件;下次启动时 Redis 读取这些文件,尽量恢复到关机前的状态。
Redis 主要提供两种方式(以及后面的混合模式):
| 方式 | 核心思路 |
|---|---|
| RDB | 某一时刻的快照(整库或按策略触发) |
| AOF | 记录写命令的日志,重放即可恢复 |
下面分别展开,并用 ASCII 图帮助理解流程。
6.2 RDB 快照
原理:定时将内存数据生成快照文件(dump.rdb)
RDB(Redis Database)把某一时刻的内存数据序列化后写成一个二进制文件,默认 often 名为 dump.rdb。
时间轴上的“拍照”
========================>
t1 t2 t3
| | |
v v v
[快照] [快照] [快照]
dump.rdb (覆盖或轮转) ...直观理解:像给数据库拍一张全景照,恢复时相当于“回到拍照那一刻”的状态,拍照之间的增量变化若没有别的机制,可能会丢。
工作流程(ASCII)
RDB 生成流程(概念)
+-------------------------------------------------------------+
| Redis 主进程 |
| +------------------+ |
| | 内存中的键值数据 | |
| +--------+---------+ |
| | |
| | 触发 SAVE / BGSAVE / 满足 save 配置 |
| v |
| +------------------+ fork() +------------------+ |
| | 可选:子进程路径 | ---------------> | 子进程 | |
| +------------------+ | 遍历内存写磁盘 | |
| | +--------+-----------+ |
| | | |
| | 主进程仍可处理读请求; v |
| | 写请求在 COW 下复制改动的页 | |
| v v |
| 继续服务客户端 写入 dump.rdb |
+-------------------------------------------------------------+触发方式
1. 手动命令
| 命令 | 行为简述 |
|---|---|
SAVE | 阻塞主进程,直到快照写完。生产环境慎用。 |
BGSAVE | 后台保存:fork 子进程写 RDB,主进程继续服务。 |
2. 配置自动触发(redis.conf)
通过 save 指令定义“在 N 秒内至少发生 M 次写”就自动 BGSAVE:
# 900 秒内至少 1 次写
save 900 1
# 300 秒内至少 10 次写
save 300 10
# 60 秒内至少 10000 次写
save 60 10000多条 save 是 或 的关系:满足任意一条就会触发一次快照(实现上多为 BGSAVE)。
3. 其他
- 正常关闭 Redis(如
SHUTDOWN)时,常会尝试生成一次 RDB(取决于配置与版本行为)。 - 主从复制等场景也可能间接触发 RDB 传递。
fork 子进程与 Copy-On-Write(简要说明)
BGSAVE 时,Redis 主进程会 fork() 出一个子进程:
fork 之后瞬间
+----------------+ +----------------+
| 父进程(Redis) | | 子进程 |
| 虚拟地址空间 | | 与父共享同一套 |
| 指向相同物理页 | | 物理内存页(只读)|
+----------------+ +----------------+
| |
| 父进程继续处理写请求 | 子进程只读内存写 RDB
v v
写某 key 时:复制被改动的内存页 读到的仍是 fork 时刻视图
(Copy-On-Write)- Copy-On-Write(写时复制):fork 后父子先共享物理页;父进程要修改某一页时,内核复制该页,父进程写新页,子进程仍读旧页。这样子进程看到的数据相当于 fork 瞬间 的快照。
- 注意:若写入非常频繁,COW 会导致复制很多页,内存与 fork 耗时都会上升,可能抖动甚至 OOM,这是 RDB/BGSAVE 在高压写场景下的经典风险点。
RDB 优缺点
| 优点 | 缺点 |
|---|---|
| 文件紧凑、适合备份与全量恢复 | 两次快照之间的数据可能丢失(宕机) |
| 恢复大数据集时通常比重放 AOF 快 | fork 在数据量大或写多时成本高 |
| 对客户端影响相对可控(BGSAVE) | 不适合要求“每条写都落盘”的强一致场景 |
6.3 AOF 日志
原理:将每条写命令追加到 appendonly.aof 文件
AOF(Append Only File)把成功的写命令按 Redis 协议格式追加到日志文件(默认 appendonly.aof)。重启时 Redis 从头到尾执行一遍这些命令,重建数据集。
写命令流
========>
SET k1 v1 ----\
INCR counter +--> 追加写入 appendonly.aof
DEL oldkey ----/
重启时:顺序重放 AOF -> 内存状态恢复工作流程(ASCII)
AOF 持久化(概念)
+-------------------------------------------------------------+
| 客户端 Redis 主进程 |
| | | |
| | 写命令 | |
| v v |
| +----------------+ |
| | 执行写操作 | |
| | 修改内存 | |
| +-------+--------+ |
| | |
| | 根据 appendfsync 策略 |
| v |
| +----------------+ |
| | 缓冲区 -> 刷盘 | --> appendonly.aof 文件 |
| +----------------+ |
+-------------------------------------------------------------+开启 AOF(redis.conf)示例:
appendonly yes
appendfilename "appendonly.aof"三种刷盘策略:appendfsync
| 取值 | 含义 | 数据安全 | 性能 |
|---|---|---|---|
| always | 每个写命令后 fsync | 最高,几乎不丢已确认的写 | 最慢 |
| everysec | 每秒 fsync 一次(默认) | 最多丢约 1 秒 内的写 | 平衡,常用 |
| no | 交给操作系统决定何时刷盘 | 可能丢更多缓冲中的数据 | 最快,风险大 |
配置示例:
# always | everysec | no
appendfsync everysecAOF 重写机制(BGREWRITEAOF)
AOF 会越写越长,很多中间状态其实可以合并成更少命令(例如对同一 key 多次修改,最终只保留最后结果)。
AOF 重写:生成一份新的、更精简的 AOF,用更少的命令表达当前内存状态。
旧 AOF(很长) 重写后 AOF(更短)
SET a 1 SET a 3
SET a 2 -----> SET b 1
SET a 3 ...
... (等价于当前数据)- 可自动触发(根据
auto-aof-rewrite-percentage、auto-aof-rewrite-min-size等配置)。 - 也可手动:
BGREWRITEAOF(后台重写)。
重写同样常用 子进程,期间新写入会同时记入 AOF 重写缓冲区,最后合并,保证不丢这段时间的写(细节实现随版本演进,初学者掌握“瘦身 + 后台”即可)。
AOF 优缺点
| 优点 | 缺点 |
|---|---|
默认 everysec 下,耐久性明显好于“纯 RDB 间隔快照” | 文件通常比 RDB 大,恢复要重放命令可能较慢 |
| 可读性相对好(文本协议),便于部分排错 | 刷盘策略选错会明显影响延迟或丢数据窗口 |
| 适合更细粒度的持久化需求 | 重写期间仍有额外 CPU/磁盘开销 |
6.4 RDB vs AOF 对比
对比表
| 对比项 | RDB | AOF |
|---|---|---|
| 存储内容 | 某一时刻内存的快照(二进制) | 写命令日志 |
| 默认是否开启 | 通常默认开启或易通过 save 触发 | 需 appendonly yes |
| 丢数据风险 | 两次快照之间宕机可能丢较多 | everysec 下通常最多约 1 秒 |
| 恢复速度 | 大数据集往往 更快 | 日志很长时可能 较慢 |
| 文件体积 | 一般 更小 | 易膨胀,需 重写 |
| 磁盘压力 | 周期性集中写入 | 持续追加 + 定期重写 |
选择建议(初学者版)
- 既要快恢复、又要少丢数据:生产常见做法是 同时开 RDB + AOF(或下一节的混合持久化),用 AOF 保耐久性,用 RDB 做备份/快速恢复辅助(具体以运维策略为准)。
- 能接受丢几分钟数据、只要简单备份:可侧重 RDB,调大
save间隔换性能(丢得更多)。 - 几乎不能丢写:AOF +
appendfsync always(性能代价大);或配合业务层双写/消息队列,不仅依赖 Redis 单机配置。 - 纯缓存、丢了能重建:可以关持久化或极宽松 RDB,但要明确业务能容忍。
6.5 混合持久化(Redis 4.0+)
RDB + AOF 混合:取两者之长
混合持久化下,AOF 文件可以是:前半段为 RDB 格式的快照数据,后半段为增量 AOF 命令。重启时先加载 RDB 部分快速构建大部分数据,再重放后面的 AOF 增量,兼顾恢复速度与耐久性。
混合 AOF 文件(示意)
+------------------------+------------------------+
| RDB 格式快照(紧凑) | 增量 AOF 命令 |
+------------------------+------------------------+
| |
+-------- 重启时先加载 RDB,再重放 AOF ---+配置方式
在 redis.conf 中(Redis 4.0+,具体选项名以你所用版本文档为准):
appendonly yes
appendfsync everysec
# 开启混合持久化(Redis 4.0+)
aof-use-rdb-preamble yes提示:不同大版本配置项可能略有调整,上线前请对照官方文档与你当前的
redis.conf模板。
本章小结
| 小节 | 核心要点 |
|---|---|
| 6.1 为什么持久化 | 内存易失;持久化把数据落到磁盘以便重启恢复。 |
| 6.2 RDB | 定时/手动快照 → dump.rdb;BGSAVE + fork + COW;快、紧凑,但可能丢两次快照间数据。 |
| 6.3 AOF | 追加写命令;always / everysec / no 控制刷盘;BGREWRITEAOF 瘦身日志。 |
| 6.4 对比与选择 | RDB 偏备份与快速全量恢复;AOF 偏少丢数据;生产常组合使用。 |
| 6.5 混合持久化 | AOF 内嵌 RDB 头 + 增量 AOF,平衡恢复速度与数据安全。 |
下一章预告
第七章:常见应用场景实战——将把前面学过的数据结构、持久化与性能意识结合起来,用贴近业务的例子(如缓存、会话、限流、排行榜等)走一遍怎么设计键、怎么选型、要注意哪些坑,让你在“会用命令”的基础上,进一步做到“会用在项目里”。