RDB、AOF 与数据恢复
系统梳理 Redis RDB、AOF、RDB+AOF、fork、Copy-on-Write、fsync、AOF rewrite、manifest、INFO persistence、启动恢复、文件校验与备份恢复流程。
第 14 章:RDB、AOF 与数据恢复
1. 本章定位
Redis 的核心数据驻留在内存中,进程退出、主机断电或容器被重新调度后,内存状态可能消失。持久化解决的是:
Redis 进程重新启动后,能够从磁盘恢复到某个历史状态。
本章不只是比较 RDB 和 AOF,还要建立三个边界:
- 持久化主要决定单节点重启后的恢复点,即 RPO。
- 高可用主要决定节点故障后能否快速切换,即 RTO。
- 备份用于应对误删除、逻辑污染、勒索软件、整机或机房损坏。
Redis 支持 RDB 快照、AOF 日志、不持久化,以及同时启用 RDB 和 AOF。Redis 7.0+ 又将 AOF 拆分为基础文件、增量文件和 manifest,改变了 AOF 重写与备份的实现方式。(Redis)
2. 学习目标
学完本章,应能够:
- 根据业务 RPO、RTO 和性能预算选择不持久化、RDB、AOF 或组合方案。
- 解释
SAVE与BGSAVE的执行方式和阻塞差异。 - 说明
fork、Copy-on-Write、fork 延迟和 COW 内存放大的因果关系。 - 比较
appendfsync always、everysec、no的性能与数据损失窗口。 - 解释 Redis 7.0+ AOF 重写期间基础文件、增量文件和 manifest 如何切换。
- 区分“同时开启 RDB+AOF”和“AOF 使用 RDB preamble”两种不同概念。
- 根据
INFO persistence、磁盘指标和日志定位持久化故障。 - 制订启动恢复、文件校验、离线修复和异地备份流程。
2.1 本章边界与跳转
本章是 RDB、AOF 和数据恢复的主章节。它回答进程重启或文件损坏后如何恢复;副本是否能接管、切主是否会丢写、读副本是否旧读,归第 15 章;Sentinel 和 Cluster 的自动故障转移分别见[第 16 章](/blog/tech/Redis/16.Sentinel 高可用/)与[第 17 章](/blog/tech/Redis/17.Redis Cluster 分片与高可用/);fork、COW、AOF fsync 造成的性能尖峰排查见第 20 章。
3. 核心概念
3.1 四种持久化模式
| 模式 | 恢复来源 | 典型数据损失窗口 | 主要优势 | 主要代价 |
|---|---|---|---|---|
| 不持久化 | 无 | 进程内全部数据 | 无持久化磁盘开销 | 重启后数据清空 |
| RDB | 最近一次成功快照 | 两次快照之间的数据 | 文件紧凑、适合备份、恢复较快 | fork、COW、快照间数据可能丢失 |
| AOF | 基础 AOF 与增量 AOF | 取决于 appendfsync | 恢复点通常更新 | 文件写入、fsync、rewrite 开销 |
| RDB+AOF | 启动时通常优先 AOF | 通常由 AOF 策略决定 | 兼顾恢复点和独立快照备份 | 同时承担两套持久化成本 |
RDB 是某一时刻完整数据集的快照;AOF 记录导致数据变化的命令,并在启动时重放或装载其基础文件。两者并非简单的“一个快、一个安全”,选型必须结合恢复目标和资源约束。(Redis)
3.2 RPO 与 RTO
RPO:Recovery Point Objective
RPO 表示发生故障时,可以接受丢失多长时间的数据。
例如:
- RPO 为 10 分钟:允许恢复到故障前最多 10 分钟的状态。
- RPO 为 1 秒:希望最多损失约 1 秒写入。
- RPO 接近 0:不仅需要更积极的持久化,还必须考虑复制确认、存储控制器和故障切换。
RTO:Recovery Time Objective
RTO 表示发生故障后,业务允许多长时间恢复服务。
影响 Redis RTO 的因素包括:
- 持久化文件大小。
- 磁盘顺序读取性能。
- AOF 中需要重放的命令数量。
- 是否使用混合 AOF。
- 数据装载期间的 CPU 和内存带宽。
- 是否存在 Sentinel、Cluster 或外部编排系统完成故障转移。
RDB 文件较紧凑,通常有利于恢复速度;未充分重写的纯文本 AOF 可能需要重放大量命令,因此恢复时间不能只根据文件字节数判断。
3.3 RDB:时间点快照
RDB 将 Redis 在某个时刻的数据集序列化到磁盘文件中。默认文件名通常是 dump.rdb,实际路径由 dir 与 dbfilename 共同决定。
SAVE
SAVE 在 Redis 主进程中同步完成快照:
- 停止处理其他客户端命令。
- 遍历数据库并生成 RDB。
- 写入完成后才恢复客户端请求。
其命令总工作量与键数量和数据字节数相关,通常可近似理解为 O(N + B):
N:键及内部元素的数量。B:需要编码、压缩和写入的字节数。
SAVE 会阻塞所有客户端,官方文档明确指出它几乎不应在生产环境的正常运行阶段使用。(Redis)
BGSAVE
BGSAVE 的命令调度本身是 O(1),但实际快照工作仍然需要遍历并写出整个数据集:
- 主进程执行
fork。 - 子进程获得 fork 时刻的数据视图。
- 子进程将数据写入临时 RDB 文件。
- 写入成功后,通过原子重命名替换正式 RDB。
- 父进程继续处理客户端请求。
因此,“BGSAVE 是 O(1)”只描述发起命令的复杂度,不代表生成快照无需 O(N) 工作。(Redis)
3.4 fork 与 Copy-on-Write
fork 是什么
在类 Unix 系统中,fork 创建子进程。操作系统不会在 fork 瞬间复制 Redis 全部物理内存,而是:
- 复制进程元数据和页表。
- 父子进程暂时共享相同物理内存页。
- 将共享页标记为写时复制。
尽管不复制全部数据,fork 仍可能造成主线程停顿。数据集越大、页表越多、主机内存压力越高,fork 停顿通常越明显。
Copy-on-Write 是什么
Copy-on-Write,简称 COW,即写时复制。
假设 fork 后某个内存页同时被父进程和子进程引用:
- 子进程需要读取 fork 时刻的旧数据,用于生成一致的 RDB。
- 父进程继续接收客户端写入。
- 当父进程修改该页时,操作系统复制这个页。
- 父进程修改新页,子进程继续看到旧页。
这使 Redis 不必长时间停止写入,也能让子进程获得逻辑一致的快照。
COW 的代价
COW 并不等于“零成本后台保存”。
假设 Redis 使用 40 GB 内存,BGSAVE 持续 30 秒,在此期间业务修改了大量不同内存页,那么额外内存可能显著增长。
近似关系是:
COW 额外内存 ≈ 快照期间被首次修改的内存页总量
需要注意:
- 同一个页被修改多次,通常只因首次写入产生一次 COW 副本。
- 修改一个很小的字段,也可能复制其所在的整个内存页。
- 写入热点集中在少量页面时,COW 成本可能有限。
- 写入均匀覆盖大部分数据集时,额外内存可能接近数据集规模。
- 子进程运行越久,父进程积累脏页的时间越长。
两类“写放大”
在持久化语境中,需要区分:
- 内存写放大:COW 复制内存页。
- 磁盘写放大:生成新快照或新 AOF 基础文件时,旧文件和新文件同时存在,业务增量还要继续写入。
因此,大内存、慢磁盘和高写入率叠加时,风险并非简单相加,而会相互强化:
磁盘越慢
→ 子进程持续时间越长
→ COW 窗口越长
→ 被修改的页面越多
→ 内存压力越大
3.5 AOF:追加写命令日志
开启 AOF 后,Redis 将改变数据集的命令追加到 AOF。启动时,Redis 根据这些内容重建数据集。
appendonly yes
AOF 记录的是命令产生的效果,而不是每次都写出完整内存镜像。AOF 文件不断增长,因此需要定期 rewrite。
三种 appendfsync 策略
| 策略 | 行为 | 典型损失窗口 | 延迟影响 | 适用性 |
|---|---|---|---|---|
always | 新命令追加后积极执行 fsync | 最小,但不能承诺绝对零丢失 | 最大 | 极高本地持久性要求、可接受写延迟 |
everysec | 大约每秒执行一次 fsync | 设计目标约 1 秒 | 通常较平衡 | 大多数需要持久化的业务 |
no | 由操作系统决定刷盘时机 | 依赖 OS,可能为数秒甚至更长 | 较低 | 可接受更大损失窗口的场景 |
Redis 的示例配置默认使用 appendfsync everysec。always 也不等于绝对不会丢数据,因为磁盘控制器缓存、文件系统、硬件故障、主从切换和网络分区都在其保证范围之外。(Redis)
write 与 fsync 的区别
write通常只是把数据交给操作系统页缓存。fsync要求操作系统将相关数据推进到持久化存储。fsync的长尾延迟高度依赖磁盘、文件系统、云盘限额和共享 I/O。
因此,AOF 已经执行 write,不代表断电后数据一定还在。
3.6 AOF rewrite
AOF 会不断记录写命令。例如:
SET counter 1
INCR counter
INCR counter
INCR counter
当前状态只需要:
SET counter 4
AOF rewrite 的目标是根据当前内存状态生成一套更紧凑的等价表示,而不是逐行读取旧 AOF 并进行字符串压缩。
Redis 7.0+ 的多部件 AOF
Redis 7.0+ 通常维护:
- 一个基础文件 Base AOF。
- 一个或多个增量文件 Incremental AOF。
- 一个 manifest,记录当前有效文件及顺序。
重写的大致过程是:
- 父进程 fork 子进程。
- 子进程根据 fork 时刻的数据生成新的基础文件。
- 父进程立即打开新的增量 AOF,后续写命令追加到该文件。
- 子进程完成后,Redis 生成新的临时 manifest。
- 原子切换 manifest。
- 清理已不再引用的旧文件。
这样,新写命令不会丢失,也不需要像旧版本那样在父进程中维护一个可能很大的 AOF rewrite 内存缓冲区。(Redis)
Redis 7.0 之前
旧版单文件 AOF 重写期间:
- 子进程生成新的 AOF。
- 父进程继续把新写命令写入旧 AOF。
- 父进程还要在内存中保存 rewrite 期间的新命令。
- 子进程完成后,父进程把内存缓冲追加到新 AOF。
- 原子替换旧文件。
因此在高写入流量下,旧方案的 rewrite buffer 可能带来额外内存压力。(Redis)
3.7 混合持久化
“混合持久化”通常指:
aof-use-rdb-preamble yes
开启后,AOF 的基础部分使用 RDB 二进制格式,之后的新写入仍以增量 AOF 记录。
其效果是:
AOF 基础文件:RDB 格式的完整状态
AOF 增量文件:基础状态之后的写命令
优势包括:
- 基础文件通常比纯命令 AOF 更紧凑。
- 启动时装载基础状态通常比重放大量命令更快。
- 保留 AOF 较小恢复点的特点。
aof-use-rdb-preamble yes 与“同时启用 RDB 和 AOF”不是同一概念:
- 前者描述 AOF 基础文件的编码格式。
- 后者表示独立生成
dump.rdb,同时维护 AOF。 - 仅开启 AOF 混合格式,并不会自动为你建立独立、按
save策略生成的 RDB 备份。
Redis 示例配置默认启用 AOF 的 RDB preamble。(GitHub)
3.8 启动恢复过程
仅开启 RDB
启动时:
- 找到
dir下的 RDB 文件。 - 校验文件格式及校验和。
- 反序列化键和值。
- 恢复过期时间等元数据。
- 数据装载完成后对外提供服务。
恢复点是最近一次成功快照。
仅开启 AOF
Redis 7.0+ 通常:
- 读取 AOF manifest。
- 装载基础文件。
- 按 manifest 顺序应用增量 AOF。
- 完成数据集重建。
- 开始提供服务。
基础文件可能是纯 AOF 格式,也可能是 RDB preamble。
同时开启 RDB 和 AOF
如果 RDB 和 AOF 都开启,Redis 启动时通常选择 AOF,因为 AOF 一般包含比最近一次 RDB 更新的状态。Redis 不会先装 RDB 再把整套 AOF 随意叠加;AOF 自身应形成完整恢复链。(Redis)
AOF 尾部截断
进程崩溃时,AOF 最后一个命令可能只写了一部分。如果开启:
aof-load-truncated yes
Redis 可以接受并忽略不完整尾部,然后启动并给出警告。
如果损坏发生在文件中间,而不是简单尾部截断,Redis 通常会拒绝启动。此时应:
- 停止对原文件的写入。
- 复制完整故障文件。
- 先运行校验工具而不修改文件。
- 确认损坏偏移和预计丢失范围。
- 再决定手工修复、使用
redis-check-aof --fix,或回退到备份。
--fix 可能丢弃损坏点之后的全部内容,不能把它当成无损修复按钮。(Redis)
3.9 数据损失窗口
| 配置 | 发生进程或主机故障时的典型损失窗口 |
|---|---|
| 不持久化 | 当前节点全部内存数据 |
| RDB | 最近一次成功 RDB 之后的写入 |
AOF always | 本地持久化窗口最小,但仍受硬件、文件系统及切换过程影响 |
AOF everysec | 设计目标约 1 秒;严重磁盘阻塞时需要结合实际指标判断 |
AOF no | 由操作系统刷盘策略决定,窗口通常更大 |
| RDB+AOF | 正常启动通常由 AOF 决定恢复点,RDB提供独立备份与回退选择 |
不能把配置间隔直接等同于上界。例如配置每 5 分钟触发一次 RDB,但上一次 BGSAVE 已失败,则实际恢复点可能更早。
3.10 持久化、高可用与备份的边界
持久化不等于高可用
单节点拥有完好的 AOF,但主机宕机后,如果没有副本和自动故障转移,业务仍然不可用。
持久化解决:
节点重新启动后,从哪里恢复数据
高可用解决:
节点故障时,谁继续提供服务
高可用不等于不丢数据
Redis 复制默认是异步的。主节点确认写入后,该写入可能尚未到达待提升副本;故障转移后,已确认写入仍可能丢失。WAIT 能降低部分风险,但不能把 Redis 复制系统转换为绝对强一致系统。(Redis)
高可用不等于备份
假设应用误执行:
FLUSHALL
则:
- AOF 会忠实记录该命令。
- RDB 下一次保存会保存清空后的状态。
- 主从复制会把删除传播给副本。
- 自动切换只会切到同样被清空的副本。
只有独立、带历史版本、最好异机或异地保存的备份,才能让系统回到误操作之前。
官方持久化文档同样建议将备份传输到宿主机或数据中心之外,并验证文件完整性和恢复能力。(Redis)
4. 命令与 Go 使用方法
4.1 查看持久化配置
redis-cli CONFIG GET save
redis-cli CONFIG GET appendonly
redis-cli CONFIG GET appendfsync
redis-cli CONFIG GET aof-use-rdb-preamble
redis-cli CONFIG GET dir
redis-cli CONFIG GET dbfilename
redis-cli CONFIG GET appenddirname
常见配置示意:
save 3600 1
save 300 100
save 60 10000
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
appenddirname "appendonlydir"
多条 save 规则是“或”关系。例如“60 秒内至少发生 10000 次修改”满足时,可以触发后台保存。实际生产配置可能与示例不同,应以实例配置和托管平台策略为准。(GitHub)
4.2 查看持久化状态
redis-cli INFO persistence
redis-cli LASTSAVE
重点关注:
loading
rdb_bgsave_in_progress
rdb_last_save_time
rdb_last_bgsave_status
rdb_last_bgsave_time_sec
rdb_last_cow_size
aof_enabled
aof_rewrite_in_progress
aof_rewrite_scheduled
aof_last_bgrewrite_status
aof_last_write_status
aof_current_size
aof_base_size
aof_pending_bio_fsync
aof_delayed_fsync
aof_last_cow_size
其中:
rdb_last_bgsave_status:最近一次 RDB 后台保存是否成功。aof_last_write_status:最近 AOF 写入是否成功。aof_delayed_fsync:AOF fsync 延迟次数是否持续增长。aof_current_size / aof_base_size:可用于判断增量增长与 rewrite 需求。*_last_cow_size:最近后台持久化的 COW 内存规模。(Redis)
4.3 手工触发 RDB
redis-cli BGSAVE
redis-cli BGSAVE SCHEDULE
redis-cli LASTSAVE
BGSAVE SCHEDULE 在已有后台保存或 AOF rewrite 时,将保存请求标记为稍后执行,而不是立即失败。该选项从 Redis 3.2.2 开始提供。(Redis)
不要在正常生产流量期间随意执行:
redis-cli SAVE
除非实例已经隔离、不再承载请求,并且明确接受阻塞。
4.4 手工触发 AOF rewrite
redis-cli BGREWRITEAOF
redis-cli INFO persistence
命令返回成功只表示:
- 重写已经启动,或者
- 因另一个后台持久化任务而被排队。
它不表示重写文件已经成功切换。最终结果必须检查:
aof_rewrite_in_progress
aof_rewrite_scheduled
aof_last_bgrewrite_status
Redis 会协调 RDB 保存和 AOF rewrite,避免两个重量级后台持久化任务同时运行。(Redis)
4.5 从 RDB 模式在线切换到 AOF
在已有数据的运行实例上启用 AOF,应遵循经过验证的在线切换流程,例如:
redis-cli CONFIG SET appendonly yes
redis-cli CONFIG REWRITE
不要仅修改磁盘上的配置文件后直接重启。否则可能出现:
- 实例重启后按照新配置寻找 AOF。
- AOF 尚未从当前内存数据建立。
- 原本只有 RDB 的数据未按预期装载。
切换前还应确认磁盘空间、rewrite 时间、COW 内存和回滚方案。官方文档明确提示,仅修改配置文件再重启可能造成数据损失。(Redis)
4.6 Go:检查和触发持久化任务
package redisops
import (
"context"
"errors"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
func NewClient(addr, username, password string) *redis.Client {
return redis.NewClient(&redis.Options{
Addr: addr,
Username: username,
Password: password,
DB: 0,
DialTimeout: 2 * time.Second,
ReadTimeout: 3 * time.Second,
WriteTimeout: 3 * time.Second,
PoolTimeout: 4 * time.Second,
})
}
// InspectPersistence 返回服务端的持久化状态。
// 调用方可以进一步解析 rdb_last_bgsave_status、aof_last_write_status 等字段。
func InspectPersistence(
parent context.Context,
client *redis.Client,
) (string, error) {
ctx, cancel := context.WithTimeout(parent, 3*time.Second)
defer cancel()
info, err := client.Info(ctx, "persistence").Result()
if err != nil {
if errors.Is(err, context.DeadlineExceeded) ||
errors.Is(err, context.Canceled) {
return "", fmt.Errorf("查询持久化状态超时或被取消: %w", err)
}
return "", fmt.Errorf("执行 INFO persistence 失败: %w", err)
}
return info, nil
}
// TriggerBGSave 只确认 BGSAVE 命令是否被 Redis 接受,
// 不代表后台快照已经成功完成。
func TriggerBGSave(
parent context.Context,
client *redis.Client,
) error {
ctx, cancel := context.WithTimeout(parent, 5*time.Second)
defer cancel()
if err := client.BgSave(ctx).Err(); err != nil {
if errors.Is(err, context.DeadlineExceeded) ||
errors.Is(err, context.Canceled) {
return fmt.Errorf(
"BGSAVE 返回状态未知,命令可能已被服务端接收,禁止盲目重试: %w",
err,
)
}
return fmt.Errorf("触发 BGSAVE 失败: %w", err)
}
return nil
}
// TriggerAOFRewrite 只负责提交重写请求。
// 最终状态应通过 INFO persistence 或监控系统判断。
func TriggerAOFRewrite(
parent context.Context,
client *redis.Client,
) error {
ctx, cancel := context.WithTimeout(parent, 5*time.Second)
defer cancel()
if err := client.BgRewriteAOF(ctx).Err(); err != nil {
if errors.Is(err, context.DeadlineExceeded) ||
errors.Is(err, context.Canceled) {
return fmt.Errorf(
"BGREWRITEAOF 返回状态未知,禁止无条件重复提交: %w",
err,
)
}
return fmt.Errorf("触发 BGREWRITEAOF 失败: %w", err)
}
return nil
}
go-redis/v9 提供 Info、BgSave、BgRewriteAOF、LastSave、ConfigGet、ConfigSet 和 ConfigRewrite 等管理命令封装。(GitHub)
关键代码说明
-
每次操作使用独立 Context
context.WithTimeout限制客户端等待时间,避免运维请求无限占用连接。 -
超时不代表命令未执行
Redis 可能已经收到
BGSAVE,但客户端在收到响应之前超时。此时自动重试会造成重复提交或错误告警。 -
命令响应不代表任务完成
BGSAVE和BGREWRITEAOF是异步后台任务。必须继续检查INFO persistence。 -
这些命令一般不会返回
redis.Nilredis.Nil通常表示读取某个不存在的键。管理命令应重点区分服务端错误、网络错误和 Context 错误。 -
并发安全边界
redis.Client可以被多个 goroutine 并发复用,它内部管理连接池。(GitHub)但
BGSAVE和BGREWRITEAOF是实例级管理操作。客户端并发安全不代表可以让多个业务 goroutine 同时触发它们。生产中应由单例运维控制器、定时任务领导者或人工变更流程串行执行。 -
不要从业务请求路径修改持久化配置
应使用独立 ACL 运维账号和配置管理流程。普通应用账号通常只需要数据命令权限。
5. 典型业务场景
| 业务场景 | 适用方案 | 不适用方案 | 数据量要求 | 一致性要求 | 主要性能风险 | 可替代方案 |
|---|---|---|---|---|---|---|
| 可随时从数据库重建的缓存 | 不持久化,或低频 RDB | 为追求形式安全强行使用 always | 受重建时长和数据库承载能力约束 | 低 | 重启后缓存雪崩、回源洪峰 | 本地缓存、预热、限流 |
| Session、登录态 | AOF everysec,必要时 RDB+AOF | 仅低频 RDB且无法接受会话回退 | 中等 | 中高 | fsync 抖动、过期数据量大 | 数据库会话、签名 Token |
| 排行榜、点赞计数 | RDB+AOF everysec,或根据数据库流水重建 | 无来源且完全不持久化 | 可较大 | 取决于是否允许少量回退 | 高频写导致 COW、AOF 增长 | 事件日志、数据库汇总 |
| 购物车、草稿 | AOF everysec,定期备份 | 仅不持久化 | 中等 | 通常不能整批丢失 | 大对象更新、rewrite COW | 关系数据库、文档数据库 |
| 延迟任务、待处理队列 | 需要 AOF、复制及业务幂等 | 仅依赖 RDB快照 | 取决于积压量 | 高,且要求可重复消费 | 恢复重放、重复执行、磁盘阻塞 | Kafka、RabbitMQ、数据库任务表 |
| 订单、余额、账务主记录 | Redis 不应作为唯一事实源 | 任何仅依赖单 Redis 的方案 | 不应成为判断依据 | 极高 | 持久化与切换窗口仍可能丢数据 | 事务数据库、账本系统 |
| 限流窗口、临时锁 | 多数情况下可不持久化 | 为短 TTL 状态引入昂贵持久化 | 小到中 | 低至中 | 重启后窗口重置 | 网关限流、本地令牌桶 |
选型的核心问题不是“数据重要不重要”这一句话,而是:
- 数据能否从其他事实源重建?
- 允许损失多少时间的写入?
- 重建时会不会压垮下游数据库?
- 是否需要历史版本以应对误操作?
- 恢复服务和恢复完整数据哪个优先?
6. 底层实现
6.1 RDB 的核心数据结构与算法
RDB 不是内存页的直接转储,而是 Redis 按数据库和键进行序列化。
概念上包括:
RDB Header
├─ 版本信息
├─ 辅助字段
├─ DB 选择标记
├─ Key 过期时间
├─ Value 类型与编码
├─ Value 内容
└─ EOF 与校验和
Redis 会根据对象类型和内部编码写入相应表示,例如 String、Hash、List、Set、Sorted Set 等。
为什么使用逻辑序列化而不是复制内存映像
直接保存进程内存映像存在问题:
- 指针地址重启后无效。
- 内存分配器布局与系统版本相关。
- 大量内部碎片没有必要写入文件。
- 数据结构编码可能需要跨版本兼容。
逻辑序列化可以获得更紧凑、可校验、可跨进程恢复的格式。
时间复杂度
SAVE:主线程完成 O(N + B) 工作,并全程阻塞。BGSAVE命令提交:O(1)。BGSAVE子进程实际工作:O(N + B)。- RDB 装载:通常为 O(N + B)。
其中 B 往往比键数量 N 更能反映真实 CPU 和磁盘成本。例如十万个 1 KB 键与十万个 1 MB 键的成本完全不同。
空间成本
磁盘峰值不能只按一个 dump.rdb 计算:
磁盘峰值 ≈ 旧 RDB + 临时新 RDB + 其他持久化文件
内存峰值还要加上:
内存峰值 ≈ 正常 RSS + COW 页面 + 子进程页表及运行开销
RDB 使用临时文件完成后再原子重命名,因此旧文件在新文件成功之前仍然有效。(Redis)
6.2 AOF 的写入路径
概念上的 AOF 写入路径可以表示为:
客户端写命令
↓
Redis 执行并修改内存
↓
生成需要传播的命令表示
↓
加入 AOF 缓冲
↓
write 到操作系统页缓存
↓
按照 appendfsync 策略执行 fsync
这里需要区分多个执行上下文:
- Redis 命令执行主要由主线程串行完成。
BGSAVE、AOF rewrite 的全量生成由子进程完成。- 某些 fsync 工作可由后台 I/O 线程处理。
- 磁盘严重阻塞仍可能通过内核写回、页缓存压力等路径影响主线程延迟。
因此,“持久化完全在后台,不影响请求”是错误结论。
6.3 AOF rewrite 的空间模型
Redis 7.0+ rewrite 期间,磁盘上可能同时存在:
旧 Base AOF
+ 旧 Incremental AOF
+ 新临时 Base AOF
+ rewrite 期间的新 Incremental AOF
+ manifest 与临时元数据
粗略容量规划不应只预留“当前 AOF 再多一点”。在最坏情况下,需要容纳旧恢复链与新恢复链的重叠。
如果同时还在生成 RDB,或实例所在磁盘还承载日志、复制缓冲落盘等任务,空间压力会进一步增大。
6.4 为什么需要 manifest
Redis 7.0+ 使用多个 AOF 文件后,需要回答:
- 哪个 Base AOF 有效?
- 哪些 Incremental AOF 属于该 Base?
- 增量文件按什么顺序应用?
- rewrite 崩溃时应该继续使用旧链还是新链?
manifest 相当于恢复链目录。Redis 先生成新的临时 manifest,确认基础文件准备完成后再原子切换,从而避免启动时看到一套半完成的新恢复链。(Redis)
6.5 混合 AOF 的恢复复杂度
纯命令 AOF 的恢复成本近似为:
O(需要重放的命令数量 + 命令字节数)
混合 AOF 的恢复成本近似为:
O(基础数据集大小 + 增量命令数量)
当历史上对同一批键反复修改时,纯 AOF 中可能存在大量已经被后续命令覆盖的操作。使用 RDB preamble 后,基础部分直接描述当前状态,减少重复执行。
这也是混合格式能够改善启动时间的重要原因。
6.6 fork 延迟与业务延迟
BGSAVE 命令本身虽快,fork 发生时主进程仍需暂停。
影响 fork 延迟的常见因素:
- Redis 进程虚拟内存规模。
- 页表规模。
- 主机 CPU 竞争。
- 内存回收和 Swap。
- 虚拟化环境抖动。
- 同机其他大进程。
fork 完成后,子进程写盘还会与主进程竞争:
- CPU 时间。
- 内存带宽。
- 磁盘带宽和 IOPS。
- 文件系统锁与内核回写资源。
因此,延迟表现通常分成两段:
- fork 瞬间的短暂停顿。
- 子进程存活期间更长的资源竞争和 COW 压力。
6.7 对吞吐量和延迟的影响
| 机制 | 吞吐量影响 | 延迟影响 |
|---|---|---|
SAVE | 快照期间几乎无法处理正常请求 | 全程阻塞 |
BGSAVE | 子进程竞争 CPU、内存和磁盘 | fork 尖峰与后台竞争 |
AOF always | fsync 频率高,写吞吐下降 | 对磁盘长尾极敏感 |
AOF everysec | 通常较平衡 | 每秒刷盘及磁盘拥塞可能形成抖动 |
AOF no | 持久化路径开销较小 | 页缓存回写仍可能抖动 |
| AOF rewrite | 全量扫描并写新基础文件 | fork、COW、磁盘竞争 |
| 启动恢复 | 恢复期间通常不能正常承载流量 | RTO 取决于装载和重放时间 |
7. 高性能、高并发、高可用分析
7.1 高性能
CPU
RDB 编码、压缩、校验和以及 AOF rewrite 都需要 CPU。子进程不会执行客户端命令,但仍与父进程争用同一组物理 CPU。
生产建议:
- 避免把 Redis CPU 使用率长期压到极限。
- 在持久化窗口观察系统 CPU,而不只看 Redis 主线程 CPU。
- 将后台保存纳入容量压测。
内存
关键风险是 COW,而不是子进程立即复制完整数据集。
需要监控:
used_memoryused_memory_rssrdb_last_cow_sizeaof_last_cow_size- 宿主机可用内存
- Swap 使用量
- 容器内存限制和 OOM 事件
网络
本地 RDB/AOF 主要消耗磁盘,不直接产生大量业务网络流量。但以下操作可能引入网络瓶颈:
- 将备份上传到对象存储。
- 主从全量同步传输 RDB。
- 故障后应用集中重连。
- 缓存清空后大量回源。
磁盘
持久化最敏感的资源通常是磁盘:
- 顺序吞吐影响 RDB 和 rewrite 持续时间。
- fsync 长尾影响 AOF 写延迟。
- 磁盘空间影响 rewrite 能否完成。
- 共享云盘的突发额度可能导致周期性延迟。
批处理
appendfsync always 并不一定机械地为每个客户端命令单独完成一次磁盘提交;同一事件循环批次内的追加可以形成一定程度的分组提交。但它仍然明显比 everysec 更依赖 fsync 性能。(Redis)
7.2 高并发
高写入率放大 COW
并发写入越多,不代表 Redis 命令被并行执行,但意味着单位时间内被修改的内存页更多。
高写入率
→ 快照期间脏页增加
→ COW 增加
→ RSS 增长
→ 内存回收或 OOM 风险上升
维护任务不能被请求风暴触发
错误做法:
每个业务实例检测到 AOF 变大
→ 各自执行 BGREWRITEAOF
这会产生:
- 重复提交。
- 大量错误和告警。
- 运维状态难以判断。
- 超时后再次重试。
正确做法是由一个具备领导者身份的控制器统一调度。
超时后的重复执行
对于普通幂等读命令,超时重试通常风险较低。
对于 BGSAVE、BGREWRITEAOF:
- 客户端超时不代表服务端没有接收。
- 操作没有业务级 request ID。
- 重试前必须查询当前状态。
- 应把“结果未知”作为独立状态处理。
流量突增
流量突增时不宜同时启动重写:
- 业务写入增加 COW。
- rewrite 增加磁盘压力。
- fsync 延迟可能上升。
- 客户端超时后重试,形成二次流量。
持久化调度应结合流量曲线,而不是只按固定整点执行。
7.3 高可用
持久化与高可用的关系是互补而不是替代:
| 能力 | 持久化 | 复制、Sentinel 或 Cluster |
|---|---|---|
| 进程重启后恢复 | 是 | 不一定 |
| 主节点故障后自动切换 | 否 | 是 |
| 避免异步复制窗口丢失 | 不能单独保证 | 不能绝对保证 |
| 抵御误删除传播 | 否 | 否 |
| 保存历史恢复点 | 需独立备份 | 否 |
网络分区
网络分区期间,旧主节点和待提升副本可能处于不同进度。即使旧主节点已把数据 fsync 到本地,故障转移也可能选择一个尚未收到该写入的副本。
因此:
- 本地 AOF 成功不等于切换后数据一定存在。
- 高可用 RPO 还取决于复制延迟和切换策略。
- 对关键写入可以结合数据库事实源、幂等日志和业务补偿。
客户端恢复行为
Redis 启动装载大文件时,客户端应:
- 使用就绪探针判断实例是否完成装载。
- 设置连接和命令超时。
- 使用指数退避与抖动。
- 避免所有应用同时无限重试。
- 在缓存型场景限制回源并发。
- 在数据型场景禁止未完成一致性校验就恢复写流量。
8. 常见错误与生产事故
8.1 在线实例误执行 SAVE
- 现象:所有命令延迟突然升高,客户端大量超时,持续时间与 RDB 生成时间接近。
- 根因:
SAVE在主进程中同步遍历数据并写盘,阻塞正常命令处理。 - 排查方法:检查命令审计、
SLOWLOG、客户端运维记录及 Redis 日志;确认故障时间附近是否执行SAVE。 - 修复方案:等待保存结束;必要时流量切换到健康副本,但要评估数据一致性。
- 如何预防:从业务 ACL 中移除管理命令权限;生产运维只允许经过审批的
BGSAVE;对SAVE建立审计告警。
8.2 BGSAVE 期间内存暴涨并被 OOM Kill
- 现象:开始后台保存后 RSS 快速上涨,随后容器或进程被 OOM Kill。
- 根因:高写入流量修改了大量页面,COW 额外内存超过宿主机或容器余量。
- 排查方法:检查
rdb_last_cow_size、主机 OOM 日志、容器事件、写 QPS、fork 和 BGSAVE 持续时间。 - 修复方案:恢复实例并控制写流量;扩大内存余量;降低数据集规模;改善磁盘速度以缩短子进程存活时间。
- 如何预防:容量规划纳入 COW;在接近峰值写入的压测下执行
BGSAVE;避免 Redis RSS 长期逼近内存上限。
8.3 AOF everysec 下延迟周期性尖峰
- 现象:命令延迟约每秒出现一次尖峰,
aof_delayed_fsync增长。 - 根因:磁盘 fsync 长尾高,或 Redis 与数据库、日志系统共享同一块繁忙磁盘。
- 排查方法:关联
aof_delayed_fsync、磁盘await、队列深度、云盘突发额度、系统日志和 Redis 延迟监控。 - 修复方案:迁移到延迟更稳定的存储;隔离磁盘;控制 rewrite 时机;降低其他 I/O 任务。
- 如何预防:上线前测 fsync 长尾而不是只测顺序吞吐;监控 P99/P999 磁盘延迟;给云盘预留性能额度。
8.4 磁盘空间不足导致 RDB 或 AOF 失败
- 现象:
rdb_last_bgsave_status:err、aof_last_write_status:err或 rewrite 失败;日志出现 no space left。 - 根因:只按当前文件大小规划容量,未考虑旧文件、新基础文件、增量文件和临时文件同时存在。
- 排查方法:检查磁盘可用空间、inode、AOF 目录、RDB 临时文件及
aof_current_size。 - 修复方案:优先扩容或释放无关空间;避免未经分析直接删除持久化文件;确认 AOF 恢复链完整。
- 如何预防:按重写峰值预留空间;同时监控字节和 inode;对磁盘剩余量建立分级告警。
8.5 AOF 频繁 rewrite
- 现象:实例持续出现 fork、磁盘高吞吐和 COW 增长,刚结束 rewrite 又很快重新开始。
- 根因:
auto-aof-rewrite-percentage、auto-aof-rewrite-min-size与写入量不匹配,或基础文件很小而增量增长很快。 - 排查方法:比较
aof_current_size与aof_base_size,检查 rewrite 配置、完成时间和业务写入率。 - 修复方案:调整触发阈值;错峰重写;优化高频覆盖写;必要时拆分数据集。
- 如何预防:根据每日写入字节数和 rewrite 时长计算阈值,而不是机械使用默认值。
8.6 AOF 损坏后直接执行 redis-check-aof --fix
- 现象:Redis 可以启动,但大量最近数据甚至大段历史数据消失。
- 根因:损坏发生在文件中部,修复工具截断了损坏点后的内容;操作前没有备份原文件。
- 排查方法:比对修复前后文件大小、工具报告的损坏偏移、业务数据版本和备份。
- 修复方案:立即停止写入;从原始故障文件副本、RDB 备份或其他节点重建;进行业务补偿。
- 如何预防:修复前复制文件;先只校验不修改;记录预计损失范围;关键数据保留独立历史备份。
8.7 同时开启 RDB 和 AOF,却只备份 dump.rdb
- 现象:恢复测试时数据比预期旧;或者生产启动使用 AOF,而备份恢复只得到较早的 RDB 状态。
- 根因:误以为“开启 RDB+AOF”意味着任意复制一个文件都能获得相同恢复点。
- 排查方法:确认实例启动时实际选择的恢复源;检查 AOF manifest、基础文件、增量文件和 RDB 时间戳。
- 修复方案:明确备份目标;需要最新恢复点时备份完整 AOF 恢复链;需要稳定历史快照时保留 RDB。
- 如何预防:为每类备份标记恢复点和恢复步骤;定期在隔离环境执行真实恢复演练。
Redis 7.0+ 备份 AOF 目录时还要避免与 rewrite 并发,否则可能复制到不一致的文件集合。(Redis)
9. 方案选型与权衡
9.1 持久化方案对比
| 方案 | RPO | 启动 RTO | 正常写延迟 | fork/COW | 磁盘成本 | 备份能力 | 适用场景 |
|---|---|---|---|---|---|---|---|
| 不持久化 | 全部内存数据 | 快,但为空数据集 | 最低 | 无持久化 fork | 最低 | 无 | 可重建缓存 |
| RDB | 快照间隔 | 通常较好 | 平时较低,快照时抖动 | 有 | 周期性全量写 | 很好 | 可接受分钟级损失 |
AOF no | OS 决定 | 取决于 AOF 大小 | 较低 | rewrite 时有 | 持续追加 | 一般 | 可接受较大损失窗口 |
AOF everysec | 约 1 秒目标 | 中等,混合格式可改善 | 中等 | rewrite 时有 | 持续追加与周期重写 | 较好 | 常见数据型 Redis |
AOF always | 本地窗口最小 | 中等 | 较高且依赖 fsync 长尾 | rewrite 时有 | 最高 | 较好 | 极重视本地持久性的少数场景 |
RDB+AOF everysec | 通常由 AOF 决定 | 启动通常使用 AOF | 中等偏高 | 两类后台任务均需考虑 | 较高 | 最完整 | 数据重要且希望保留独立快照 |
9.2 业务选型建议
| 业务条件 | 建议起点 | 额外措施 |
|---|---|---|
| 数据完全可由数据库重建 | 不持久化或低频 RDB | 防止重建雪崩、预热和回源限流 |
| 可接受数分钟数据回退 | RDB | 异地备份、监控快照成功状态 |
| 可接受约 1 秒窗口 | AOF everysec | 混合 AOF、定期 RDB备份 |
| 本地写入丢失窗口需尽量缩小 | AOF always,谨慎压测 | 复制确认、独立事实源、低延迟存储 |
| 既要较新恢复点又要时间点备份 | RDB+AOF everysec | 完整备份与恢复演练 |
| Redis 只是关键数据的派生视图 | AOF everysec 或 RDB | 保持数据库、事件日志为事实源 |
| Redis 承担唯一订单或账务记录 | 不建议 | 改用具备事务与审计能力的数据库 |
9.3 面试中如何回答“RDB 和 AOF 哪个更好”
推荐回答框架:
RDB 和 AOF 没有脱离业务目标的绝对优劣。RDB 是时间点快照,文件紧凑、适合备份、恢复通常较快,但数据损失窗口取决于快照频率,并有 fork 和 COW 成本。AOF 记录增量写入,配合 everysec 通常把损失窗口控制在约一秒,但会增加持续写盘、fsync 和 rewrite 开销。Redis 7.0+ 使用多部件 AOF,混合格式还可以用 RDB 作为基础文件。重要数据一般考虑 RDB+AOF,并配合复制和异地备份;可重建缓存则可能完全不需要持久化。
回答时至少覆盖五个维度:
- RPO。
- RTO。
- 运行时延迟。
- 磁盘和内存成本。
- 是否有独立备份及事实源。
10. 高频面试题
问题 1:RDB 和 AOF 哪个更好?
推荐回答
**现场简答:**没有绝对更好。RDB 更适合紧凑快照、快速恢复和备份;AOF 通常能提供更小的数据损失窗口,但有持续写盘、fsync 和 rewrite 成本。重要数据常用 RDB+AOF,可重建缓存可以不持久化。
详细来说,应该先确定 RPO 和 RTO,再评估磁盘延迟、内存余量和重启恢复时间。不能只根据“安全性”一个维度选择。
面试官追问
- 同时开启后启动加载哪一个?
- 为什么官方不建议只依赖 AOF?
- 哪些场景可以完全关闭持久化?
常见错误回答
“AOF 永远不会丢数据,所以一定比 RDB 好。”
评分点
- 初级:能说明 RDB 是快照、AOF 是日志。
- 中级:能比较 RPO、RTO、fsync 和 rewrite。
- 高级:能进一步讨论 COW、备份、高可用边界及业务事实源。
问题 2:SAVE 和 BGSAVE 有什么区别?
推荐回答
现场简答:SAVE 由主进程同步生成 RDB,会阻塞客户端;BGSAVE 先 fork 子进程,由子进程生成 RDB,父进程继续处理请求,但仍有 fork 停顿、COW 和资源竞争。
BGSAVE 命令提交是 O(1),实际保存工作依然是 O(N + B),不能据此认为其没有性能成本。
面试官追问
BGSAVE是否完全不阻塞?- 为什么不用线程而使用子进程?
BGSAVE失败后如何发现?
常见错误回答
“BGSAVE 完全在后台,所以对线上没有影响。”
评分点
- 初级:知道
SAVE阻塞、BGSAVE后台执行。 - 中级:能解释 fork 和 COW。
- 高级:能区分命令复杂度和总工作量,并说明监控指标。
问题 3:什么是 Copy-on-Write?
推荐回答
**现场简答:**fork 后父子进程共享物理页。当父进程修改某个共享页时,操作系统复制该页,父进程修改副本,子进程继续读取 fork 时刻的旧页,这就是 COW。
COW 避免 fork 时复制整个 Redis 数据集,但会在快照期间产生额外内存。额外内存与被修改的页面数量相关,而不只是写命令次数。
面试官追问
- 修改一个很小的字段为什么也可能复制整个页?
- COW 内存是否一定等于数据集大小?
- 如何降低 COW 风险?
常见错误回答
“fork 后子进程马上拥有一份完整内存副本。”
评分点
- 初级:能描述写时复制。
- 中级:知道额外成本按内存页产生。
- 高级:能结合写入分布、子进程时长和内存容量分析风险。
问题 4:为什么 BGSAVE 仍可能造成延迟尖峰?
推荐回答
**现场简答:**首先 fork 本身会暂停主进程;其次子进程运行期间会竞争 CPU、内存带宽和磁盘;高写入还会产生大量 COW,引发 RSS 增长甚至内存回收。
故障排查不能只看 BGSAVE 是否正在运行,还要关联 fork 耗时、COW 大小、磁盘延迟和主机内存压力。
面试官追问
- 数据量相同,为什么两次 fork 时间可能不同?
- 慢磁盘为什么会扩大 COW?
- 容器内存限制有什么额外风险?
常见错误回答
“只有 SAVE 会阻塞,BGSAVE 对延迟没有影响。”
评分点
- 初级:知道 fork 会短暂停顿。
- 中级:能说明资源竞争。
- 高级:能建立“慢磁盘—长子进程—更多 COW”的因果链。
问题 5:三种 appendfsync 策略如何选择?
推荐回答
现场简答:always 本地持久性最强但写延迟最高;everysec 大约每秒刷盘,是性能与数据损失窗口的常见平衡;no 交给操作系统刷盘,性能较好但损失窗口不可精确控制。
选型必须通过实际磁盘 P99/P999 fsync 延迟压测,不能只看平均吞吐量。
面试官追问
always是否绝对零丢失?everysec是否一定最多只丢一秒?write成功和fsync成功有什么区别?
常见错误回答
“everysec 保证任何情况下只丢一秒数据。”
评分点
- 初级:能说出三种策略的性能顺序。
- 中级:能解释页缓存和 fsync。
- 高级:能讨论磁盘长尾、故障切换和保证边界。
问题 6:AOF rewrite 是否读取并压缩旧 AOF?
推荐回答
**现场简答:**通常不是。AOF rewrite 根据当前内存数据集生成一套能够重建当前状态的更紧凑表示,而不是逐条解析旧 AOF 再做文本压缩。
这样可以自然消除已经被覆盖的写入。例如某个键更新一万次,重写后可能只保留描述最终状态所需的命令。
面试官追问
- rewrite 期间的新写命令放在哪里?
- rewrite 失败会不会破坏旧 AOF?
- 为什么 rewrite 也需要 fork?
常见错误回答
“AOF rewrite 就是把旧日志做 gzip 压缩。”
评分点
- 初级:知道 rewrite 会缩小文件。
- 中级:知道它基于当前数据集重建。
- 高级:能讲清 Redis 7.0+ Base、Incremental 和 manifest。
问题 7:Redis 7.0+ 如何处理 AOF 重写期间的新写入?
推荐回答
**现场简答:**父进程 fork 子进程生成新 Base AOF,同时父进程将后续写入追加到新的 Incremental AOF。子进程完成后,通过新 manifest 原子切换到新的恢复链。
这避免了旧版本中父进程需要长期保存 AOF rewrite 内存缓冲的主要问题。
面试官追问
- Redis 7.0 以前是怎么做的?
- manifest 的作用是什么?
- 为什么备份 AOF 目录要避开 rewrite?
常见错误回答
“重写期间 Redis 暂停所有写入。”
评分点
- 初级:知道业务写入不会停止。
- 中级:能说明增量命令需要被保留。
- 高级:能比较 Redis 7 前后的实现和备份一致性。
问题 8:什么是混合持久化?
推荐回答
**现场简答:**通常指 aof-use-rdb-preamble yes:AOF 基础文件采用 RDB 二进制格式,基础状态之后的修改继续记录为增量 AOF。
它改善文件大小和启动速度,但不等于同时开启独立 RDB 快照。是否生成 dump.rdb 仍由 save 等配置决定。
面试官追问
- 混合 AOF 启动时如何恢复?
- 它是否仍然属于 AOF?
- 为什么不能把它等同于 RDB+AOF?
常见错误回答
“混合持久化就是 Redis 同时加载 dump.rdb 和 appendonly.aof。”
评分点
- 初级:知道它结合了 RDB 和 AOF 表示。
- 中级:能说明 Base 与 Incremental。
- 高级:能准确区分文件编码与独立持久化策略。
问题 9:RDB 和 AOF 同时存在时,Redis 启动加载哪个?
推荐回答
**现场简答:**两者都开启时,Redis 通常优先使用 AOF,因为 AOF 一般包含更完整、更近期的状态。
但如果 AOF 损坏,Redis 不应被理解为一定会自动安全回退到 RDB。生产恢复应先保护故障文件,再由运维人员评估 AOF 修复、RDB 回退和数据损失范围。
面试官追问
- 回退 RDB 会损失多少数据?
- AOF 尾部截断和中间损坏有何区别?
- 如何验证恢复后的业务完整性?
常见错误回答
“Redis 会把 RDB 和 AOF 简单合并,所以不会丢数据。”
评分点
- 初级:知道优先加载 AOF。
- 中级:能区分截断和损坏。
- 高级:能描述完整恢复流程与业务校验。
问题 10:磁盘满了会发生什么?
推荐回答
**现场简答:**RDB 保存、AOF 追加或 AOF rewrite 都可能失败。具体业务行为取决于失败类型和配置,但必须立即关注 rdb_last_bgsave_status、aof_last_write_status、日志、磁盘空间和 inode。
处理时不能为了腾空间随意删除当前 AOF 文件,因为 Redis 7.0+ 的恢复链由 manifest、基础文件和增量文件共同组成。
面试官追问
- 为什么 rewrite 需要额外磁盘空间?
- 磁盘有空间但仍失败可能是什么原因?
- 如何设置告警阈值?
常见错误回答
“磁盘满只影响下次重启,不影响当前运行。”
评分点
- 初级:知道持久化会失败。
- 中级:能检查状态和日志。
- 高级:能进行重写峰值容量规划并保护恢复链。
问题 11:持久化、高可用和备份有什么区别?
推荐回答
**现场简答:**持久化解决节点重启后的数据恢复;高可用解决节点故障后的服务切换;备份提供独立历史版本,用于误删除、逻辑污染和灾难恢复。三者不能互相替代。
主从和 Cluster 会传播错误写入,AOF 也会记录错误命令,因此没有历史备份就无法可靠回到误操作之前。
面试官追问
- 为什么有三个副本仍然需要备份?
- AOF
always为什么不等于高可用? - 备份应该如何验证?
常见错误回答
“有 Sentinel 和三个副本就不需要备份。”
评分点
- 初级:能给出三者定义。
- 中级:能举误删除传播的例子。
- 高级:能结合 RPO、RTO、异地备份和恢复演练回答。
问题 12:Go 中调用 BGSAVE 超时后应该直接重试吗?
推荐回答
**现场简答:**不应该盲目重试。客户端超时只表示没有及时收到结果,命令可能已被 Redis 接收并启动。应先查询 INFO persistence,确认 rdb_bgsave_in_progress、最近保存状态和时间,再决定后续动作。
redis.Client 可供多个 goroutine 并发使用,但实例级运维任务仍应由单一控制器串行调度。
面试官追问
- Context 取消会终止服务端 BGSAVE 吗?
- 为什么管理命令需要独立 ACL?
- 如何防止多个应用实例重复调度?
常见错误回答
“只要 Go 返回 timeout,就说明 Redis 没执行,可以无限重试。”
评分点
- 初级:知道要使用 Context 和检查错误。
- 中级:知道超时存在执行结果不确定性。
- 高级:能讨论领导者调度、状态机、ACL 和运维幂等性。
11. 一分钟面试回答
Redis 持久化主要有 RDB 和 AOF。RDB 是时间点快照,文件紧凑、适合备份且恢复通常较快,但两次快照之间的数据可能丢失;SAVE 会阻塞主线程,生产通常用 BGSAVE。BGSAVE 通过 fork 子进程和 COW 获得一致快照,但 fork 会造成短暂停顿,高写入期间还可能产生大量 COW 内存。AOF 记录增量写命令,appendfsync 有 always、everysec 和 no,常见平衡是 everysec。AOF 会不断增长,因此需要 rewrite;Redis 7.0+ 使用 Base AOF、Incremental AOF 和 manifest,重写期间父进程继续把新写入追加到新的增量文件。混合持久化通常指 AOF 基础文件使用 RDB preamble,不等同于独立开启 RDB+AOF。两者同时开启时,启动通常优先 AOF。最后,持久化不等于高可用,高可用也不等于备份;关键数据还需要复制、事实源、异地备份和恢复演练。
12. 本章总结
- RDB 与 AOF 的核心差异是时间点快照和增量操作日志,而不是简单的快慢之分。
BGSAVE仍会产生 fork 停顿、COW 内存和资源竞争。- AOF 的安全性由 fsync 策略、磁盘质量和实际故障类型共同决定。
- Redis 7.0+ 的多部件 AOF通过 Base、Incremental 和 manifest 改善了 rewrite 流程。
- 混合 AOF 是文件编码方案,不等于独立 RDB+AOF 双持久化。
- 两者同时开启时,正常启动通常优先 AOF,RDB仍然非常适合作为独立快照备份。
- 容量规划必须同时考虑旧文件、新文件、增量文件、临时文件和 COW。
- 持久化、高可用、复制和备份分别解决不同问题,任何一个都不能独立构成完整的数据安全体系。
13. 自测清单
- 为什么
BGSAVE命令复杂度是 O(1),却仍可能消耗大量 CPU 和磁盘? - fork 时是否会立即复制 Redis 的全部物理内存?
- 哪些业务写入模式最容易造成较大的 COW?
appendfsync everysec的“一秒数据损失窗口”有哪些边界条件?- Redis 7.0+ AOF rewrite 期间的新写入保存在哪里?
- manifest 在多部件 AOF 中解决了什么问题?
aof-use-rdb-preamble yes与同时开启 RDB+AOF 有何区别?- RDB 和 AOF 都开启时,Redis 启动通常选择哪个恢复源?
- 为什么三个 Redis 副本仍然不能替代历史备份?
- Go 调用
BGREWRITEAOF超时后,为什么不能立即无条件重试?
14. 官方资料
- Redis 官方持久化文档:RDB、AOF、rewrite、启动恢复与备份。(Redis)
- Redis 官方
redis.conf示例:save、appendfsync、AOF rewrite、RDB preamble 等配置。(GitHub) - Redis 官方
SAVE、BGSAVE与LASTSAVE命令文档。(Redis) - Redis 官方
INFO persistence字段说明。(Redis) - Redis 官方
BGREWRITEAOF、CONFIG GET与CONFIG REWRITE命令文档。(Redis) - Redis 官方复制文档:异步复制、故障转移及数据损失边界。(Redis)
- Redis 官方 Go 客户端
go-redis/v9文档与源码。(GitHub)