返回文章列表

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. 学习目标

学完本章,应能够:

  1. 根据业务 RPO、RTO 和性能预算选择不持久化、RDB、AOF 或组合方案。
  2. 解释 SAVEBGSAVE 的执行方式和阻塞差异。
  3. 说明 fork、Copy-on-Write、fork 延迟和 COW 内存放大的因果关系。
  4. 比较 appendfsync alwayseverysecno 的性能与数据损失窗口。
  5. 解释 Redis 7.0+ AOF 重写期间基础文件、增量文件和 manifest 如何切换。
  6. 区分“同时开启 RDB+AOF”和“AOF 使用 RDB preamble”两种不同概念。
  7. 根据 INFO persistence、磁盘指标和日志定位持久化故障。
  8. 制订启动恢复、文件校验、离线修复和异地备份流程。

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,实际路径由 dirdbfilename 共同决定。

SAVE

SAVE 在 Redis 主进程中同步完成快照:

  1. 停止处理其他客户端命令。
  2. 遍历数据库并生成 RDB。
  3. 写入完成后才恢复客户端请求。

其命令总工作量与键数量和数据字节数相关,通常可近似理解为 O(N + B)

  • N:键及内部元素的数量。
  • B:需要编码、压缩和写入的字节数。

SAVE 会阻塞所有客户端,官方文档明确指出它几乎不应在生产环境的正常运行阶段使用。(Redis)

BGSAVE

BGSAVE 的命令调度本身是 O(1),但实际快照工作仍然需要遍历并写出整个数据集:

  1. 主进程执行 fork
  2. 子进程获得 fork 时刻的数据视图。
  3. 子进程将数据写入临时 RDB 文件。
  4. 写入成功后,通过原子重命名替换正式 RDB。
  5. 父进程继续处理客户端请求。

因此,“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 成本可能有限。
  • 写入均匀覆盖大部分数据集时,额外内存可能接近数据集规模。
  • 子进程运行越久,父进程积累脏页的时间越长。

两类“写放大”

在持久化语境中,需要区分:

  1. 内存写放大:COW 复制内存页。
  2. 磁盘写放大:生成新快照或新 AOF 基础文件时,旧文件和新文件同时存在,业务增量还要继续写入。

因此,大内存、慢磁盘和高写入率叠加时,风险并非简单相加,而会相互强化:

磁盘越慢
→ 子进程持续时间越长
→ COW 窗口越长
→ 被修改的页面越多
→ 内存压力越大

3.5 AOF:追加写命令日志

开启 AOF 后,Redis 将改变数据集的命令追加到 AOF。启动时,Redis 根据这些内容重建数据集。

appendonly yes

AOF 记录的是命令产生的效果,而不是每次都写出完整内存镜像。AOF 文件不断增长,因此需要定期 rewrite。

三种 appendfsync 策略

策略行为典型损失窗口延迟影响适用性
always新命令追加后积极执行 fsync最小,但不能承诺绝对零丢失最大极高本地持久性要求、可接受写延迟
everysec大约每秒执行一次 fsync设计目标约 1 秒通常较平衡大多数需要持久化的业务
no由操作系统决定刷盘时机依赖 OS,可能为数秒甚至更长较低可接受更大损失窗口的场景

Redis 的示例配置默认使用 appendfsync everysecalways 也不等于绝对不会丢数据,因为磁盘控制器缓存、文件系统、硬件故障、主从切换和网络分区都在其保证范围之外。(Redis)

writefsync 的区别

  • 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,记录当前有效文件及顺序。

重写的大致过程是:

  1. 父进程 fork 子进程。
  2. 子进程根据 fork 时刻的数据生成新的基础文件。
  3. 父进程立即打开新的增量 AOF,后续写命令追加到该文件。
  4. 子进程完成后,Redis 生成新的临时 manifest。
  5. 原子切换 manifest。
  6. 清理已不再引用的旧文件。

这样,新写命令不会丢失,也不需要像旧版本那样在父进程中维护一个可能很大的 AOF rewrite 内存缓冲区。(Redis)

Redis 7.0 之前

旧版单文件 AOF 重写期间:

  1. 子进程生成新的 AOF。
  2. 父进程继续把新写命令写入旧 AOF。
  3. 父进程还要在内存中保存 rewrite 期间的新命令。
  4. 子进程完成后,父进程把内存缓冲追加到新 AOF。
  5. 原子替换旧文件。

因此在高写入流量下,旧方案的 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

启动时:

  1. 找到 dir 下的 RDB 文件。
  2. 校验文件格式及校验和。
  3. 反序列化键和值。
  4. 恢复过期时间等元数据。
  5. 数据装载完成后对外提供服务。

恢复点是最近一次成功快照。

仅开启 AOF

Redis 7.0+ 通常:

  1. 读取 AOF manifest。
  2. 装载基础文件。
  3. 按 manifest 顺序应用增量 AOF。
  4. 完成数据集重建。
  5. 开始提供服务。

基础文件可能是纯 AOF 格式,也可能是 RDB preamble。

同时开启 RDB 和 AOF

如果 RDB 和 AOF 都开启,Redis 启动时通常选择 AOF,因为 AOF 一般包含比最近一次 RDB 更新的状态。Redis 不会先装 RDB 再把整套 AOF 随意叠加;AOF 自身应形成完整恢复链。(Redis)

AOF 尾部截断

进程崩溃时,AOF 最后一个命令可能只写了一部分。如果开启:

aof-load-truncated yes

Redis 可以接受并忽略不完整尾部,然后启动并给出警告。

如果损坏发生在文件中间,而不是简单尾部截断,Redis 通常会拒绝启动。此时应:

  1. 停止对原文件的写入。
  2. 复制完整故障文件。
  3. 先运行校验工具而不修改文件。
  4. 确认损坏偏移和预计丢失范围。
  5. 再决定手工修复、使用 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

不要仅修改磁盘上的配置文件后直接重启。否则可能出现:

  1. 实例重启后按照新配置寻找 AOF。
  2. AOF 尚未从当前内存数据建立。
  3. 原本只有 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 提供 InfoBgSaveBgRewriteAOFLastSaveConfigGetConfigSetConfigRewrite 等管理命令封装。(GitHub)

关键代码说明

  1. 每次操作使用独立 Context

    context.WithTimeout 限制客户端等待时间,避免运维请求无限占用连接。

  2. 超时不代表命令未执行

    Redis 可能已经收到 BGSAVE,但客户端在收到响应之前超时。此时自动重试会造成重复提交或错误告警。

  3. 命令响应不代表任务完成

    BGSAVEBGREWRITEAOF 是异步后台任务。必须继续检查 INFO persistence

  4. 这些命令一般不会返回 redis.Nil

    redis.Nil 通常表示读取某个不存在的键。管理命令应重点区分服务端错误、网络错误和 Context 错误。

  5. 并发安全边界

    redis.Client 可以被多个 goroutine 并发复用,它内部管理连接池。(GitHub)

    BGSAVEBGREWRITEAOF实例级管理操作。客户端并发安全不代表可以让多个业务 goroutine 同时触发它们。生产中应由单例运维控制器、定时任务领导者或人工变更流程串行执行。

  6. 不要从业务请求路径修改持久化配置

    应使用独立 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。
  • 文件系统锁与内核回写资源。

因此,延迟表现通常分成两段:

  1. fork 瞬间的短暂停顿。
  2. 子进程存活期间更长的资源竞争和 COW 压力。

6.7 对吞吐量和延迟的影响

机制吞吐量影响延迟影响
SAVE快照期间几乎无法处理正常请求全程阻塞
BGSAVE子进程竞争 CPU、内存和磁盘fork 尖峰与后台竞争
AOF alwaysfsync 频率高,写吞吐下降对磁盘长尾极敏感
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_memory
  • used_memory_rss
  • rdb_last_cow_size
  • aof_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

这会产生:

  • 重复提交。
  • 大量错误和告警。
  • 运维状态难以判断。
  • 超时后再次重试。

正确做法是由一个具备领导者身份的控制器统一调度。

超时后的重复执行

对于普通幂等读命令,超时重试通常风险较低。

对于 BGSAVEBGREWRITEAOF

  • 客户端超时不代表服务端没有接收。
  • 操作没有业务级 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:erraof_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-percentageauto-aof-rewrite-min-size 与写入量不匹配,或基础文件很小而增量增长很快。
  • 排查方法:比较 aof_current_sizeaof_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 noOS 决定取决于 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,并配合复制和异地备份;可重建缓存则可能完全不需要持久化。

回答时至少覆盖五个维度:

  1. RPO。
  2. RTO。
  3. 运行时延迟。
  4. 磁盘和内存成本。
  5. 是否有独立备份及事实源。

10. 高频面试题

问题 1:RDB 和 AOF 哪个更好?

推荐回答

**现场简答:**没有绝对更好。RDB 更适合紧凑快照、快速恢复和备份;AOF 通常能提供更小的数据损失窗口,但有持续写盘、fsync 和 rewrite 成本。重要数据常用 RDB+AOF,可重建缓存可以不持久化。

详细来说,应该先确定 RPO 和 RTO,再评估磁盘延迟、内存余量和重启恢复时间。不能只根据“安全性”一个维度选择。

面试官追问

  1. 同时开启后启动加载哪一个?
  2. 为什么官方不建议只依赖 AOF?
  3. 哪些场景可以完全关闭持久化?

常见错误回答

“AOF 永远不会丢数据,所以一定比 RDB 好。”

评分点

  • 初级:能说明 RDB 是快照、AOF 是日志。
  • 中级:能比较 RPO、RTO、fsync 和 rewrite。
  • 高级:能进一步讨论 COW、备份、高可用边界及业务事实源。

问题 2:SAVEBGSAVE 有什么区别?

推荐回答

现场简答:SAVE 由主进程同步生成 RDB,会阻塞客户端;BGSAVE 先 fork 子进程,由子进程生成 RDB,父进程继续处理请求,但仍有 fork 停顿、COW 和资源竞争。

BGSAVE 命令提交是 O(1),实际保存工作依然是 O(N + B),不能据此认为其没有性能成本。

面试官追问

  1. BGSAVE 是否完全不阻塞?
  2. 为什么不用线程而使用子进程?
  3. BGSAVE 失败后如何发现?

常见错误回答

BGSAVE 完全在后台,所以对线上没有影响。”

评分点

  • 初级:知道 SAVE 阻塞、BGSAVE 后台执行。
  • 中级:能解释 fork 和 COW。
  • 高级:能区分命令复杂度和总工作量,并说明监控指标。

问题 3:什么是 Copy-on-Write?

推荐回答

**现场简答:**fork 后父子进程共享物理页。当父进程修改某个共享页时,操作系统复制该页,父进程修改副本,子进程继续读取 fork 时刻的旧页,这就是 COW。

COW 避免 fork 时复制整个 Redis 数据集,但会在快照期间产生额外内存。额外内存与被修改的页面数量相关,而不只是写命令次数。

面试官追问

  1. 修改一个很小的字段为什么也可能复制整个页?
  2. COW 内存是否一定等于数据集大小?
  3. 如何降低 COW 风险?

常见错误回答

“fork 后子进程马上拥有一份完整内存副本。”

评分点

  • 初级:能描述写时复制。
  • 中级:知道额外成本按内存页产生。
  • 高级:能结合写入分布、子进程时长和内存容量分析风险。

问题 4:为什么 BGSAVE 仍可能造成延迟尖峰?

推荐回答

**现场简答:**首先 fork 本身会暂停主进程;其次子进程运行期间会竞争 CPU、内存带宽和磁盘;高写入还会产生大量 COW,引发 RSS 增长甚至内存回收。

故障排查不能只看 BGSAVE 是否正在运行,还要关联 fork 耗时、COW 大小、磁盘延迟和主机内存压力。

面试官追问

  1. 数据量相同,为什么两次 fork 时间可能不同?
  2. 慢磁盘为什么会扩大 COW?
  3. 容器内存限制有什么额外风险?

常见错误回答

“只有 SAVE 会阻塞,BGSAVE 对延迟没有影响。”

评分点

  • 初级:知道 fork 会短暂停顿。
  • 中级:能说明资源竞争。
  • 高级:能建立“慢磁盘—长子进程—更多 COW”的因果链。

问题 5:三种 appendfsync 策略如何选择?

推荐回答

现场简答:always 本地持久性最强但写延迟最高;everysec 大约每秒刷盘,是性能与数据损失窗口的常见平衡;no 交给操作系统刷盘,性能较好但损失窗口不可精确控制。

选型必须通过实际磁盘 P99/P999 fsync 延迟压测,不能只看平均吞吐量。

面试官追问

  1. always 是否绝对零丢失?
  2. everysec 是否一定最多只丢一秒?
  3. write 成功和 fsync 成功有什么区别?

常见错误回答

everysec 保证任何情况下只丢一秒数据。”

评分点

  • 初级:能说出三种策略的性能顺序。
  • 中级:能解释页缓存和 fsync。
  • 高级:能讨论磁盘长尾、故障切换和保证边界。

问题 6:AOF rewrite 是否读取并压缩旧 AOF?

推荐回答

**现场简答:**通常不是。AOF rewrite 根据当前内存数据集生成一套能够重建当前状态的更紧凑表示,而不是逐条解析旧 AOF 再做文本压缩。

这样可以自然消除已经被覆盖的写入。例如某个键更新一万次,重写后可能只保留描述最终状态所需的命令。

面试官追问

  1. rewrite 期间的新写命令放在哪里?
  2. rewrite 失败会不会破坏旧 AOF?
  3. 为什么 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 内存缓冲的主要问题。

面试官追问

  1. Redis 7.0 以前是怎么做的?
  2. manifest 的作用是什么?
  3. 为什么备份 AOF 目录要避开 rewrite?

常见错误回答

“重写期间 Redis 暂停所有写入。”

评分点

  • 初级:知道业务写入不会停止。
  • 中级:能说明增量命令需要被保留。
  • 高级:能比较 Redis 7 前后的实现和备份一致性。

问题 8:什么是混合持久化?

推荐回答

**现场简答:**通常指 aof-use-rdb-preamble yes:AOF 基础文件采用 RDB 二进制格式,基础状态之后的修改继续记录为增量 AOF。

它改善文件大小和启动速度,但不等于同时开启独立 RDB 快照。是否生成 dump.rdb 仍由 save 等配置决定。

面试官追问

  1. 混合 AOF 启动时如何恢复?
  2. 它是否仍然属于 AOF?
  3. 为什么不能把它等同于 RDB+AOF?

常见错误回答

“混合持久化就是 Redis 同时加载 dump.rdbappendonly.aof。”

评分点

  • 初级:知道它结合了 RDB 和 AOF 表示。
  • 中级:能说明 Base 与 Incremental。
  • 高级:能准确区分文件编码与独立持久化策略。

问题 9:RDB 和 AOF 同时存在时,Redis 启动加载哪个?

推荐回答

**现场简答:**两者都开启时,Redis 通常优先使用 AOF,因为 AOF 一般包含更完整、更近期的状态。

但如果 AOF 损坏,Redis 不应被理解为一定会自动安全回退到 RDB。生产恢复应先保护故障文件,再由运维人员评估 AOF 修复、RDB 回退和数据损失范围。

面试官追问

  1. 回退 RDB 会损失多少数据?
  2. AOF 尾部截断和中间损坏有何区别?
  3. 如何验证恢复后的业务完整性?

常见错误回答

“Redis 会把 RDB 和 AOF 简单合并,所以不会丢数据。”

评分点

  • 初级:知道优先加载 AOF。
  • 中级:能区分截断和损坏。
  • 高级:能描述完整恢复流程与业务校验。

问题 10:磁盘满了会发生什么?

推荐回答

**现场简答:**RDB 保存、AOF 追加或 AOF rewrite 都可能失败。具体业务行为取决于失败类型和配置,但必须立即关注 rdb_last_bgsave_statusaof_last_write_status、日志、磁盘空间和 inode。

处理时不能为了腾空间随意删除当前 AOF 文件,因为 Redis 7.0+ 的恢复链由 manifest、基础文件和增量文件共同组成。

面试官追问

  1. 为什么 rewrite 需要额外磁盘空间?
  2. 磁盘有空间但仍失败可能是什么原因?
  3. 如何设置告警阈值?

常见错误回答

“磁盘满只影响下次重启,不影响当前运行。”

评分点

  • 初级:知道持久化会失败。
  • 中级:能检查状态和日志。
  • 高级:能进行重写峰值容量规划并保护恢复链。

问题 11:持久化、高可用和备份有什么区别?

推荐回答

**现场简答:**持久化解决节点重启后的数据恢复;高可用解决节点故障后的服务切换;备份提供独立历史版本,用于误删除、逻辑污染和灾难恢复。三者不能互相替代。

主从和 Cluster 会传播错误写入,AOF 也会记录错误命令,因此没有历史备份就无法可靠回到误操作之前。

面试官追问

  1. 为什么有三个副本仍然需要备份?
  2. AOF always 为什么不等于高可用?
  3. 备份应该如何验证?

常见错误回答

“有 Sentinel 和三个副本就不需要备份。”

评分点

  • 初级:能给出三者定义。
  • 中级:能举误删除传播的例子。
  • 高级:能结合 RPO、RTO、异地备份和恢复演练回答。

问题 12:Go 中调用 BGSAVE 超时后应该直接重试吗?

推荐回答

**现场简答:**不应该盲目重试。客户端超时只表示没有及时收到结果,命令可能已被 Redis 接收并启动。应先查询 INFO persistence,确认 rdb_bgsave_in_progress、最近保存状态和时间,再决定后续动作。

redis.Client 可供多个 goroutine 并发使用,但实例级运维任务仍应由单一控制器串行调度。

面试官追问

  1. Context 取消会终止服务端 BGSAVE 吗?
  2. 为什么管理命令需要独立 ACL?
  3. 如何防止多个应用实例重复调度?

常见错误回答

“只要 Go 返回 timeout,就说明 Redis 没执行,可以无限重试。”

评分点

  • 初级:知道要使用 Context 和检查错误。
  • 中级:知道超时存在执行结果不确定性。
  • 高级:能讨论领导者调度、状态机、ACL 和运维幂等性。

11. 一分钟面试回答

Redis 持久化主要有 RDB 和 AOF。RDB 是时间点快照,文件紧凑、适合备份且恢复通常较快,但两次快照之间的数据可能丢失;SAVE 会阻塞主线程,生产通常用 BGSAVEBGSAVE 通过 fork 子进程和 COW 获得一致快照,但 fork 会造成短暂停顿,高写入期间还可能产生大量 COW 内存。AOF 记录增量写命令,appendfsyncalwayseverysecno,常见平衡是 everysec。AOF 会不断增长,因此需要 rewrite;Redis 7.0+ 使用 Base AOF、Incremental AOF 和 manifest,重写期间父进程继续把新写入追加到新的增量文件。混合持久化通常指 AOF 基础文件使用 RDB preamble,不等同于独立开启 RDB+AOF。两者同时开启时,启动通常优先 AOF。最后,持久化不等于高可用,高可用也不等于备份;关键数据还需要复制、事实源、异地备份和恢复演练。


12. 本章总结

  1. RDB 与 AOF 的核心差异是时间点快照增量操作日志,而不是简单的快慢之分。
  2. BGSAVE 仍会产生 fork 停顿、COW 内存和资源竞争。
  3. AOF 的安全性由 fsync 策略、磁盘质量和实际故障类型共同决定。
  4. Redis 7.0+ 的多部件 AOF通过 Base、Incremental 和 manifest 改善了 rewrite 流程。
  5. 混合 AOF 是文件编码方案,不等于独立 RDB+AOF 双持久化。
  6. 两者同时开启时,正常启动通常优先 AOF,RDB仍然非常适合作为独立快照备份。
  7. 容量规划必须同时考虑旧文件、新文件、增量文件、临时文件和 COW。
  8. 持久化、高可用、复制和备份分别解决不同问题,任何一个都不能独立构成完整的数据安全体系。

13. 自测清单

  1. 为什么 BGSAVE 命令复杂度是 O(1),却仍可能消耗大量 CPU 和磁盘?
  2. fork 时是否会立即复制 Redis 的全部物理内存?
  3. 哪些业务写入模式最容易造成较大的 COW?
  4. appendfsync everysec 的“一秒数据损失窗口”有哪些边界条件?
  5. Redis 7.0+ AOF rewrite 期间的新写入保存在哪里?
  6. manifest 在多部件 AOF 中解决了什么问题?
  7. aof-use-rdb-preamble yes 与同时开启 RDB+AOF 有何区别?
  8. RDB 和 AOF 都开启时,Redis 启动通常选择哪个恢复源?
  9. 为什么三个 Redis 副本仍然不能替代历史备份?
  10. Go 调用 BGREWRITEAOF 超时后,为什么不能立即无条件重试?

14. 官方资料

  • Redis 官方持久化文档:RDB、AOF、rewrite、启动恢复与备份。(Redis)
  • Redis 官方 redis.conf 示例:saveappendfsync、AOF rewrite、RDB preamble 等配置。(GitHub)
  • Redis 官方 SAVEBGSAVELASTSAVE 命令文档。(Redis)
  • Redis 官方 INFO persistence 字段说明。(Redis)
  • Redis 官方 BGREWRITEAOFCONFIG GETCONFIG REWRITE 命令文档。(Redis)
  • Redis 官方复制文档:异步复制、故障转移及数据损失边界。(Redis)
  • Redis 官方 Go 客户端 go-redis/v9 文档与源码。(GitHub)