Redis 的高性能很大程度上来自内存访问,但内存具有易失性:进程退出、操作系统重启或服务器断电后,内存中的数据都会消失。
如果 Redis 只用于保存可以重新计算的缓存,这种行为可能完全可以接受。但当 Redis 保存登录会话、排行榜、延迟任务或尚未处理的事件时,我们通常希望服务重启后还能恢复数据。Redis 持久化正是为这个问题设计的。
持久化并不是简单地“把内存复制到磁盘”。Redis 需要在四个目标之间权衡:
- 数据安全性:故障发生时,最多允许丢失多少数据?
- 恢复速度:故障发生后,最多允许多长时间恢复服务?
- 运行性能:持久化能否避免长期阻塞客户端请求?
- 资源成本:生成快照、追加日志和恢复数据需要多少内存、CPU、磁盘空间与 I/O?
故障后的数据损失和恢复速度通常用两个恢复目标表达:恢复点目标(Recovery Point Objective,RPO)表示故障后允许恢复到多早之前的数据状态,恢复时间目标(Recovery Time Objective,RTO)表示从故障发生到服务恢复可用最多允许多长时间。
Redis 提供了两条主要持久化路径:RDB 定期保存某个时间点的完整数据集,AOF 持续记录能够重建数据集的写操作。本文将沿着数据进入持久存储、Redis 启动恢复和备份验证的顺序,解释二者的机制、选择与边界。
版本说明:本文以 Redis Open Source 7.0 及以上版本的多段 AOF 机制为主,并补充 Redis Open Source 8.10.0 开始提供的在线
BACKUP机制。Redis 7.0 以前使用单文件 AOF,其重写和备份流程与多段 AOF 不同。
1. 持久化解决什么问题,又不能解决什么
Redis 持久化是指将内存中的数据状态写入持久存储,使 Redis 在重新启动时能够根据这些文件重建数据集。
但在实际系统中,“Redis 出故障”可能代表进程崩溃、主节点宕机、磁盘损坏、误删除数据,甚至整个机房不可用。这些故障并不由同一种机制解决。

图中最重要的结论是:持久化负责重启后的数据重建,高可用负责节点故障后的服务接管,备份负责历史状态和灾难恢复。三者可以组合,但不能互相替代。
这里的“恢复”有明确边界。不同故障需要不同机制处理:
| 故障场景 | 本地持久化能否解决 | 还需要什么 |
|---|---|---|
| Redis 进程崩溃 | 通常可以恢复到最近的持久化状态 | 正确的启动和恢复流程 |
| 操作系统重启或断电 | 取决于 RDB 时间点或 AOF 刷盘进度 | 合理的持久化策略和可靠存储 |
| Redis 所在磁盘损坏 | 通常不能 | 异机或异地备份 |
执行了错误的 DEL、FLUSHDB |
新持久化文件可能保留错误结果 | 历史备份或时间点恢复方案 |
| 主节点故障,需要快速接管 | 持久化本身不会自动接管流量 | 复制、Sentinel 或 Cluster |
| 整个机房不可用 | 本机文件无法发挥作用 | 跨故障域副本和备份 |
1.1 持久化负责重新构建实例数据
Redis 运行期间,客户端写命令先修改内存数据,RDB 或 AOF 再把可恢复状态写入持久存储;Redis 重新启动时,则加载或重放这些文件,重新构建键、值和 TTL 等内存状态。
这条路径主要处理进程崩溃、操作系统重启,以及持久卷仍然存在时的实例重建。它不会自动完成节点故障检测、流量切换或跨故障域恢复。
1.2 高可用负责服务连续性
复制、Sentinel 和 Redis Cluster 解决的是主节点不可用后的故障检测、角色切换和流量接管。Redis 复制通常是异步的,因此故障切换成功也不代表没有数据窗口:主节点已经确认、但尚未复制完成的写入仍可能丢失。
1.3 备份负责跨越本地故障域
故障域是可能因同一故障而同时失效的一组基础设施,例如同一块磁盘、同一台主机或同一个可用区。
备份需要位于另一台机器、对象存储或其他故障域,用于处理本地磁盘或持久卷损坏、误执行 DEL 或 FLUSHDB、错误数据传播到副本,以及整个可用区或机房不可用等情况。
明确这三条恢复路径后,Redis Open Source 的持久化方式可以概括为四种选择:
| 方式 | 保存内容 | 典型数据损失窗口 | 主要特点 |
|---|---|---|---|
| 不持久化 | 不写入持久化文件 | Redis 内全部数据 | 性能边界简单,适合可重建缓存 |
| RDB | 某个时间点的完整数据集 | 两次快照之间的数据 | 文件紧凑,便于备份,加载通常较快 |
| AOF | 能够重建数据集的写操作 | 由 appendfsync 策略决定 |
数据更新更及时,文件和写入成本通常更高 |
| RDB + AOF | 同时生成两类文件 | 通常由 AOF 决定 | AOF 作为正常启动恢复源,RDB 提供独立时间点快照、历史归档和人工灾难恢复依据 |
这不是“哪个功能更高级”的比较。RDB 的时间间隔主要影响 RPO,文件加载速度影响 RTO;AOF 的刷盘策略主要影响 RPO,日志规模和重放速度也会影响 RTO。后文会沿着这两个目标解释每种机制的边界。
2. RDB:如何拍摄一个时间点快照
RDB(Redis Database)会把某个时间点的数据集编码成紧凑的二进制文件,默认文件名通常是 dump.rdb。
2.1 快照如何触发
可以通过 save 配置自动触发快照:
save 3600 1
save 300 100
save 60 10000
每条配置都表示一个需要同时满足时间与变更次数的触发条件。
例如:
save 60 1000
表示距上次成功保存超过 60 秒,并且此后累计至少 1000 次数据变更时,Redis 尝试启动后台快照。这里统计的不是任意连续 60 秒内的滚动窗口;对同一个键执行多次写操作,也可能累计为多次变更。多条 save 规则之间是“任意一条同时满足时间和变更次数即可触发”的关系。
也可以通过命令主动触发:
redis-cli BGSAVE
BGSAVE 在后台生成快照,Redis 主进程仍然可以继续处理请求。另一个命令 SAVE 会在主线程中同步生成快照并阻塞其他客户端,通常不应用于承载正常流量的生产实例。
2.2 Redis 如何在继续处理请求时生成快照
RDB 最容易产生的误解,是认为 Redis 需要先暂停服务,再复制一份完整内存。实际过程依赖 fork() 和操作系统的写时复制机制。

一次后台快照可以分为 T0、T1、T2 三个阶段:子进程保留 fork() 时刻的数据视图,主进程继续处理写请求,被修改的内存页才按需触发 COW;子进程完成临时文件后,再原子替换正式 RDB。
Redis 调用 fork() 创建子进程后,父子进程最初共享相同的物理内存页。父进程或子进程只要修改共享页,操作系统就会为写入方复制相应内存页,这就是写时复制(Copy-on-Write,COW)。在 RDB 生成过程中,子进程主要读取快照数据,因此额外的 COW 内存通常主要来自父进程继续处理新的写请求。
因此,创建子进程并不等于立刻复制整个 Redis 数据集。子进程看到的是开始快照时的数据视图,主进程则继续处理新的写操作:
快照开始时:counter = 100
快照期间: 客户端执行 INCR,counter = 101
RDB 内容: counter = 100
内存内容: counter = 101
这正是“时间点快照”的含义:RDB 保存的是快照开始时的数据状态,而不是生成文件完成时的最新状态。
2.3 RDB 的数据窗口和资源成本
RDB 的优势主要有三点:
- 完成后的文件包含完整数据集,结构紧凑且不会继续被追加修改。
- 大数据集从 RDB 加载,通常比逐条重放大量 AOF 命令更快。
- 正常请求不需要为每条写命令持续追加日志。
这些特点让 RDB 便于复制、归档和传输。如果需要保留多个历史恢复点,可以按小时、天或周归档不同版本的 RDB,而不是只保留 Redis 数据目录中的最新文件。
但是,快照的时间点也直接决定了故障时的数据损失窗口。假设每五分钟生成一次快照:
10:00 生成 RDB
10:04 Redis 异常退出
重新启动后最多只能恢复到 10:00,10:00 到 10:04 之间的修改可能全部丢失。
提高快照频率可以缩小 RPO,却会增加 fork()、CPU 和磁盘 I/O 的频率。大数据集还会带来三个资源问题:
fork()在主线程执行,需要复制页表等进程元数据,数据越大,客户端延迟尖峰越可能明显。- 快照期间如果写入密集,大量内存页触发 COW,实际内存占用可能显著上升。
- 子进程写入 RDB 会消耗 CPU、内存带宽和磁盘吞吐,并可能与业务 I/O 竞争。
所以 maxmemory 能限制数据集使用的内存,却不代表后台持久化不再需要额外内存。容量规划必须为 COW、复制缓冲和操作系统预留空间。
3. AOF:一条写命令什么时候才算持久化
AOF(Append Only File)的思路与 RDB 不同。它不等待下一次完整快照,而是记录 Redis 执行的数据修改操作。
3.1 AOF 记录的是什么
启用 AOF:
appendonly yes
假设客户端依次执行:
SET article:view 0
INCR article:view
INCR article:view
AOF 会保存能够表达这些修改的命令。Redis 重新启动时读取 AOF,并按顺序重放其中的操作,最终重新得到:
article:view = 2
3.2 从 Redis 缓冲区到存储介质
“写入 AOF”并不是一个单独动作。理解 AOF 的关键,不是 Redis 有没有把命令交给文件系统,而是数据当前处于 Redis 缓冲区、操作系统页缓存,还是已经同步到持久存储。

这条刷盘链路分为三层:Redis 执行命令、修改内存并追加到 AOF 缓冲区;write() 把数据交给操作系统页缓存;fsync 或 fdatasync 再请求文件系统和块设备层把数据同步到存储介质。
write() 成功只表示数据已经交给操作系统,并不必然表示数据已经到达持久存储介质。操作系统仍可能把数据保留在页缓存中。真正的数据安全边界主要由 appendfsync 策略决定。
3.3 always、everysec 与 no
Redis 提供三种策略:
| 配置 | 刷盘行为 | 性能与数据边界 |
|---|---|---|
appendfsync always |
每批写入都执行刷盘,并在回复前等待 | 数据损失窗口最小,但写入延迟和吞吐成本最高 |
appendfsync everysec |
后台线程大约每秒刷盘一次 | 常用折中;进程或主机故障时,正常情况下可能丢失约一秒写入 |
appendfsync no |
Redis 不主动刷盘,由操作系统决定 | 性能开销较小,但丢失窗口取决于内核和存储环境 |
常见配置是:
appendonly yes
appendfsync everysec
everysec 不是“绝对只会丢一秒数据”的形式化保证。磁盘长时间阻塞、操作系统异常或底层存储没有兑现刷盘语义时,实际故障边界仍受运行环境影响。因此需要把它理解为正常条件下的持久化目标,并通过监控和故障演练验证目标环境。
always 也不应该被简单理解为“完全不会丢数据”。它显著缩小 Redis 已确认写入后的丢失窗口,但无法替代可靠硬件、文件系统、备份和应用层正确性设计。
3.4 刷盘延迟和资源竞争
write() 和 fsync 都可能因为磁盘繁忙、共享存储抖动或文件系统行为而变慢。即使 everysec 把刷盘放在后台线程中,过慢的刷盘仍可能导致缓冲积压并影响主线程写入。如果 Redis 与数据库、日志系统或其他 I/O 密集服务共享同一块磁盘,后台持久化也可能让少数最慢请求的延迟,也就是尾延迟,变得更加明显。
不要把
no-appendfsync-on-rewrite yes当作常规性能优化。
它会在 BGSAVE、BGREWRITEAOF 等后台保存期间跳过 Redis 主进程的显式 fsync,但仍会通过 write() 把 AOF 数据写入操作系统页缓存。这样做可以缓解磁盘 I/O 竞争,代价是这段时间的耐久性会退化到接近 appendfsync no。
在默认 Linux 参数下,官方配置说明提示最坏场景可能丢失约 30 秒日志。该配置默认是 no,只有确认存在相关延迟,并且业务接受更大 RPO 时才应考虑修改。
4. AOF 重写与 Redis 7+ 多段文件机制
4.1 为什么 AOF 需要重写
如果 AOF 永远记录所有历史命令,文件会持续增长。下面的操作执行一百万次:
INCR article:view
恢复当前状态并不一定需要保存一百万条 INCR。只要记录最终状态对应的最小操作,例如:
SET article:view 1000000
就能得到相同结果。
Redis 通过 AOF 重写(rewrite)生成能够重建当前数据集的精简文件。重写可以自动触发,也可以手动执行:
redis-cli BGREWRITEAOF
重写不是简单地逐行压缩旧 AOF。Redis 根据当前内存数据生成新的基础表示,同时继续接收新的写操作,完成后再切换到新的 AOF 文件集合。
4.2 重写期间的写入与多段文件
Redis 7.0 以后,重写生成的基础状态和重写期间的新写入由不同文件承担,并通过清单文件(manifest)管理加载顺序和切换。

一次多段 AOF 重写会经历重写前、重写期间和重写完成后三个阶段。主进程继续把新写操作写入增量文件,后台子进程根据 fork() 时刻的数据视图生成新的基础文件,最后通过清单文件原子切换文件集合。
从 Redis 7.0 开始,AOF 使用多段文件机制。默认的 appenddirname 是 appendonlydir,目录大致如下:
appendonlydir/
├── appendonly.aof.manifest
├── appendonly.aof.*.base.rdb 或 appendonly.aof.*.base.aof
└── appendonly.aof.*.incr.aof
具体文件名可能随版本和配置变化,但职责可以概括为:
- 基础文件(base file):最多一个,表示最近一次重写时的数据基础状态,可以采用 RDB 或 AOF 格式。
- 增量文件(incremental file):一个或多个,记录基础文件之后发生的增量写操作。
- 清单文件(manifest):记录 Redis 应按什么顺序组合这些文件。
开始重写时,Redis 主进程会打开新的增量文件承接后续写入,子进程则生成新的基础文件。两部分准备完成后,Redis 使用临时清单文件记录新的加载顺序,再通过原子替换清单文件让这组 AOF 生效,并清理不再需要的旧文件。
多段 AOF 并不会消除日志增长,AOF 总规模仍然需要通过重写控制。它的核心改进,是把“重写生成的基础状态”和“重写期间及之后的增量写入”放在不同文件中,并由清单文件管理加载顺序和原子切换。
Redis 7.0 以前,主进程需要一边继续写旧 AOF,一边在内存中积累重写期间的变更,最后再把这些变更追加到子进程生成的新 AOF。多段 AOF 减少了这部分内存缓冲和重写结束阶段合并增量数据的成本,但仍然存在 fork()、COW 和磁盘 I/O 开销。
多段 AOF 也改变了备份方式:Redis 7+ 备份 AOF 时需要把清单文件、基础文件和全部关联增量文件视为一个完整文件集合,不能只复制其中某个 .aof 文件。
这里需要区分两个概念:AOF 基础文件采用 RDB 编码,只是多段 AOF 的一种内部表示方式,并不等于 Redis 已经启用了独立的 RDB 快照。所谓“同时启用 RDB 和 AOF”,是指 Redis 一方面按照 save 规则生成独立的 dump.rdb,另一方面维护自己的 AOF 文件集合;启动时也不会把这两套文件随意拼接。
4.3 自动重写条件与资源成本
自动重写通常由下面两个配置共同控制:
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
Redis 会记录最近一次成功重写后的 AOF 基准大小;如果本次启动后还没有完成过重写,则使用启动时确定的大小作为基准。只有当前 AOF 大于 auto-aof-rewrite-min-size,并且相对基准的增长比例达到 auto-aof-rewrite-percentage 时,Redis 才会尝试自动重写。
例如比例为 100,表示当前 AOF 相对基准增长了 100%,也就是大小大约达到基准的两倍,而不是“达到基准的 100%”。将 auto-aof-rewrite-percentage 设为 0 只会关闭自动重写,不会禁止手工执行 BGREWRITEAOF。
自动重写阈值需要结合 AOF 增长速度、可用内存、磁盘吞吐和延迟基线设置。
5. Redis 启动时到底加载哪份数据
Redis 启动时需要先把持久化内容恢复到内存,然后才能基于完整数据集正常提供服务。
磁盘中存在文件,并不代表 Redis 一定会读取这份文件,也不代表读取失败后会自动选择另一份恢复源。

未配置 preload-file 时,Redis 首先根据是否启用 AOF 选择恢复分支;AOF 文件集合缺失和文件损坏也不是同一种失败结果。
5.1 从 RDB 恢复
Redis 读取 RDB 文件,校验并解析其中的对象,再重建键、值和过期时间等内存状态。恢复结果对应某个快照时间点。
文件越大、对象越多,启动加载时间通常越长;Redis 在数据加载完成前不能承担正常业务流量。如果没有启用 AOF,也不存在可加载的 RDB,Redis 可以从空数据集启动。
5.2 从 AOF 恢复
Redis 7.0 及以上版本会读取清单文件,加载基础文件,再按顺序重放增量 AOF,最终重建内存状态。
AOF 文件越大、需要重放的增量越多,启动恢复时间越可能增加。AOF 重写不只是为了节省磁盘空间,也是在控制重放成本。Redis 7.0 之前使用单一 AOF 文件,启动时直接读取并重放该文件,不存在多段 AOF 的基础文件、增量文件和清单文件。
5.3 同时存在 RDB 和 AOF 时使用谁
当 RDB 与 AOF 同时启用时,Redis 重启会使用 AOF 恢复,因为 AOF 通常包含比最近一次 RDB 快照更完整的数据。
这意味着“同时启用”并不是启动时先加载独立 RDB、再随意选择一份 AOF,而是由 Redis 按其持久化规则选择恢复来源。运维人员不能因为目录中存在一个较新的 dump.rdb,就手工覆盖 AOF 文件或删除清单文件。
Redis 进程启动成功也不代表预期数据已经恢复。预期持久化文件不存在时,实例可能以空数据集启动;恢复验证必须核对启动日志、预期键数量、关键值和 TTL,而不能只检查端口是否可用。
5.4 preload-file 与实际 RTO
Redis Open Source 8.10 使用 preload-file 恢复已封存(sealed)的备份时,会绕过普通 appenddirname 和 dump.rdb 的选择逻辑,只加载指定文件或清单关联的文件集合;目标不存在或无法加载时,启动会失败。具体恢复配置放在第 7 章说明。
持久化文件让数据可以恢复,却不保证恢复是瞬时的。RDB 的 RTO 受文件大小、对象数量和解析成本影响;AOF 还受基础文件大小、增量文件数量和命令重放量影响。高可用系统应把文件加载、数据核对和应用恢复读写都纳入 RTO,而不是只测量进程拉起时间。
6. 如何根据 RPO、RTO 和数据价值选择策略
持久化策略应该由数据价值、恢复目标和资源成本共同决定,而不是机械地使用同一份配置。

选择策略时,应先看数据价值、允许的数据损失窗口和重建难度。RTO 还要结合文件规模、命令重放量、硬件性能、拓扑恢复和应用验证时间单独评估。
| 场景 | 建议起点 | 原因 |
|---|---|---|
| 页面缓存、可重新计算结果 | 不持久化,或低频 RDB | 数据能够从权威来源重建 |
| 排行榜、统计结果,能容忍几分钟损失 | RDB | 文件紧凑,备份和恢复方便 |
| 会话、任务状态、短期业务数据 | AOF everysec,必要时再保留 RDB |
在性能和数据损失窗口之间折中 |
| 重要但仍适合 Redis 的数据 | RDB + AOF + 异机备份 | 同时保留追加日志和历史快照能力 |
| 订单、余额、库存等核心权威状态 | 优先由具备业务约束和审计能力的权威数据库负责 | Redis 持久化只负责重建 Redis 状态,不能替代业务一致性、关系约束、审计和补偿机制 |
对于延迟任务、消费状态和未处理事件,AOF 持久化本身只能降低 Redis 进程重启造成的数据丢失,不能自动保证任务不丢失或不重复执行,也不提供消费确认、超时重试、死信处理和端到端的恰好一次(exactly-once)语义。
如果任务不能丢失或不能重复执行,还需要设计任务 ID、幂等处理、领取与确认状态、超时重试和失败补偿。使用复制或自动故障切换时,还要考虑异步复制尚未同步完成的数据窗口,并根据可靠性要求判断权威状态是否应该保存在数据库或专用消息系统中。
一份常见但仍需按环境调整的配置如下:
dir /data
appendonly yes
appendfsync everysec
save 3600 1
save 300 100
save 60 10000
这个示例表达的策略是:
- AOF 持续记录写入,在刷盘正常工作的前提下,将故障时的目标数据损失窗口控制在秒级。
- RDB 保留时间点快照,便于独立归档和恢复。
/data必须位于具有足够空间和可靠性的持久存储上。
它不是可以不经容量评估直接复制到所有生产环境的“最佳配置”。写入量、数据集大小、恢复时间、磁盘延迟和成本要求不同,最终策略也应不同。
7. 生产环境的监控、备份与恢复
“配置已经启用”只能证明 Redis 被要求执行持久化,不能证明持久化一直成功,更不能证明文件可以恢复。

文件上传只完成了备份流程的一部分。只有经过完整文件生成、跨故障域复制、隔离恢复、数据核对和应用验证,备份才能成为可用的恢复依据。
7.1 持久存储与监控
Redis 的 dir、RDB 文件和 AOF 子目录必须位于符合恢复目标的可靠存储上,并对容量、文件数量上限(inode)、延迟和故障建立告警。如果恢复目标包含主机或实例重建,数据存储也必须能够跨越这类重建;本机临时盘不能被当作备份。
Redis 运行在容器中时,通常需要把 /data 挂载到持久卷。例如:
services:
redis:
image: redis:8.10.0
command:
- redis-server
- --dir
- /data
- --appendonly
- "yes"
- --appendfsync
- everysec
volumes:
- redis-data:/data
volumes:
redis-data:
示例固定到撰写时 Docker Official Image 已经发布的 Redis 8.10.0,生产环境还可以进一步固定镜像摘要。这个 Compose 片段只演示持久卷和持久化参数,并不是完整生产配置;认证、网络隔离、资源限制、健康检查、监控和备份仍需单独配置。
如果文件只存在于容器可写层,删除或重新创建容器时,RDB/AOF 可能一起消失。持久卷也不等于备份,仍然需要独立的备份、监控和恢复验证。
可靠存储还需要持续监控。可以查看:
redis-cli INFO persistence
redis-cli INFO stats
前一个命令提供 RDB、AOF 和 COW 等持久化状态,后一个命令用于取得 latest_fork_usec 等运行统计。
监控字段可以按观察目的分组:
| 关注方向 | 关键字段 | 观察目的 |
|---|---|---|
COW 与 fork() |
current_cow_peak、current_cow_size、current_cow_size_age、latest_fork_usec |
判断后台任务的额外内存和创建子进程耗时;latest_fork_usec 位于 INFO stats |
| RDB | rdb_changes_since_last_save、rdb_bgsave_in_progress、rdb_last_save_time、rdb_last_bgsave_status、rdb_last_bgsave_time_sec、rdb_last_cow_size |
判断快照是否按预期启动、完成和更新 |
| AOF 写入与重写 | aof_enabled、aof_rewrite_in_progress、aof_rewrite_scheduled、aof_last_bgrewrite_status、aof_last_write_status、aof_last_cow_size |
判断 AOF 写入和重写是否正常 |
| AOF 规模与刷盘 | aof_current_size、aof_base_size、aof_pending_bio_fsync、aof_delayed_fsync |
观察文件增长、刷盘积压和磁盘延迟 |
| 在线备份 | backup_in_progress、BACKUP STATUS |
判断 Redis 8.10 备份是否正在执行,并查看完整状态、错误和时间信息 |
INFO 字段会随 Redis 版本和启用功能变化,监控采集程序应允许字段缺失或新增。
应至少为以下情况建立告警:
- 最近一次 RDB 保存失败,或者存在待保存变更但快照长时间未成功更新。
- AOF 写入或重写失败,或者
aof_current_size相对aof_base_size持续扩大。 aof_pending_bio_fsync持续不为0,或者aof_delayed_fsync在观察窗口内持续增长。current_cow_peak接近为后台任务预留的内存,或者latest_fork_usec明显偏离历史基线。- 数据盘空间或可用
inode不足。 fsync、fork或后台持久化出现异常延迟。
还需要理解 stop-writes-on-bgsave-error 的可用性影响。该配置默认是 yes:启用了 RDB save 规则且最近一次后台保存失败时,Redis 会拒绝可能修改数据集的命令;后台保存恢复成功后才重新允许写入。
stop-writes-on-bgsave-error 只控制 RDB 后台保存失败时的行为,不控制 AOF 错误。开启 AOF 后,如果写入 AOF 或后台 fsync 失败,Redis 也会拒绝可能修改数据集的命令;当策略是 appendfsync always 时,AOF 写入或同步 fsync 失败会使 Redis 退出,因为实例已经无法继续兑现“回复客户端前完成刷盘”的承诺。
因此,磁盘空间不足、目录权限错误或持久卷异常不仅是数据安全问题,也可能直接成为可用性事件。监控不能只观察 RDB 保存状态,还要同时观察 aof_last_write_status、后台刷盘积压和 Redis 进程状态。
是否关闭该配置,取决于业务更重视“阻止无法持久化的新写入”还是“在持久化失败时继续提供写服务”。这是一项需要监控、告警和补偿方案共同支撑的业务决策,不应为了消除报错直接修改。
7.2 生成和复制一致性备份
RDB 文件适合生成带时间戳的历史备份,并转移到另一台机器、对象存储或其他故障域。完整的备份流程至少包括:
- 明确保留周期。
- 校验文件大小或摘要。
- 配置访问权限和静态加密。
- 为备份失败建立告警。
- 定期执行恢复演练。
Redis Open Source 8.10 及以上:在线 BACKUP
Redis Open Source 8.10.0 开始提供 BACKUP 命令族,可以在不中止写入、也不需要手工关闭 AOF 重写的情况下生成一组自包含、可恢复的文件。备份采用与多段 AOF 兼容的格式:
- 基础文件(BASE)保存某个时间点的数据快照。
- 增量文件(INCR)记录基础文件生成后到备份封存时的写入。
- 清单文件(manifest)只描述本次基础文件和增量文件的加载关系。
BACKUP START 不要求实例已经启用 AOF。启用 AOF 时,Redis 会复用多段 AOF 和重写机制;没有启用 AOF 时,Redis 会临时启动相关机制,但不会改变 appendonly 的配置值。因此它仍可能产生 fork()、COW 和磁盘 I/O 成本,不能把在线备份理解为没有运行开销。
开始备份前,需要确认三项约束:
- 备份文件位于
dir下由backupdirname指定的目录,默认目录名是backupdir。 backupdirname只能是单个目录名,不能写成绝对路径或嵌套路径,并且只能在启动时配置。- 执行
BACKUP START时目录必须为空,一个实例同时只能进行一个备份。
backup-sealed-ttl 默认是 0,表示进入 sealed(已封存)状态的文件会一直保留到显式执行 BACKUP CLEANUP。如果设置为非零值,自动清理时间必须晚于外部复制和校验完成时间。
典型流程如下:
redis-cli BACKUP START
redis-cli BACKUP STATUS
# 重复查询,直到 state 变为 incrementing
redis-cli BACKUP LIST
# 此时可以先复制已经固定的 BASE
redis-cli BACKUP SEAL
redis-cli BACKUP STATUS
# 确认 state 为 sealed
redis-cli BACKUP LIST
# 将最终返回的 BASE、INCR 和 manifest 全部复制到独立备份存储
redis-cli BACKUP CLEANUP
生成基础文件
BACKUP START 是异步操作。需要重复查询 BACKUP STATUS:进入 incrementing 后,基础文件已经固定,Redis 会继续把新写入记录到增量文件;如果进入 failed,则应停止流程并根据 error 排查原因。此时也可以通过 BACKUP LIST 先复制已经固定的基础文件。
封存恢复边界
BACKUP SEAL 会刷新并 fsync 增量文件、生成独立清单文件,并把恢复边界固定在执行该命令的时刻。状态进入 sealed 后,BACKUP LIST 才会返回完整且不可变的基础文件、增量文件和清单文件集合。
复制和清理
必须在外部复制和校验全部完成后执行 BACKUP CLEANUP,否则本地备份文件会被删除。未封存的备份可以通过 BACKUP ABORT 中止;任意阶段都可以使用 BACKUP STATUS 查看 state、error、start_time 和 end_time。
这组命令属于 Redis Open Source 8.10.0。Redis Software、Redis Cloud 或其他托管服务是否支持,需要以对应产品文档为准。
恢复已封存(sealed)的备份时,应把基础文件、增量文件和清单文件放在同一目录,并在 Redis 启动配置中将 preload-file 指向清单文件:
preload-file aof:/restore/appendonly.aof.manifest
preload-file 是启动期配置。设置后,Redis 只加载指定文件或清单关联的文件,不再加载普通 appenddirname 或 dump.rdb,也不会把多个恢复源合并;目标不存在或无法加载时,Redis 会终止启动。预加载完成后,实例再按照正常配置继续执行后续持久化。
Redis 7.0 至 8.8,或不支持 BACKUP 的部署:手工备份
Redis 7+ 的 AOF 由清单文件、基础文件和增量文件组成。如果在重写过程中复制目录,可能得到彼此不匹配的文件集合。手工备份时,不能只在复制前检查一次状态,还要在整个复制期间阻止新的重写:
- 使用
CONFIG GET auto-aof-rewrite-percentage记录自动重写比例的原值。 - 执行
CONFIG SET auto-aof-rewrite-percentage 0,关闭自动重写。 - 在备份变更窗口内禁止手工执行
BGREWRITEAOF,并通过INFO persistence等待aof_rewrite_in_progress和aof_rewrite_scheduled都变为0。 - 将配置项
appenddirname指向的整个目录作为一个文件集合复制,不能只复制清单文件、基础文件或某个增量文件。 - 无论复制成功还是失败,都恢复
auto-aof-rewrite-percentage的原值,并检查自动重写策略已经恢复。
仅仅观察到一次 aof_rewrite_in_progress:0 并不够,因为检查完成后自动重写仍可能开始。如果备份流程必须容忍复制期间 Redis 重启,需要在关闭自动重写后执行 CONFIG REWRITE,并在恢复原值后再次执行 CONFIG REWRITE,把两个阶段的配置都持久化。
CONFIG REWRITE 会修改 Redis 使用的配置文件,不适合所有只读配置、容器参数或集中配置环境;如果 Redis 启动时没有使用配置文件,该命令会报错。无法安全执行它时,可以在复制前后核对 INFO server 中的 run_id,一旦发现实例重启,就丢弃本次候选备份并重新开始。
run_id 只能检测重启,不能让手工流程具备重启容错。需要容忍实例重启时,应使用能够持久化临时配置的流程,或者改用 Redis 8.10 的在线 BACKUP。
复制时间较长时,可以使用经过验证的一致性存储快照,或者先为 appenddirname 中的文件创建硬链接,确认全部硬链接建立完成后再尽快恢复自动重写。硬链接只能建立在同一文件系统内;外部备份仍然必须包含完整文件集合并完成独立校验。
7.3 隔离恢复与数据验证
无论备份来自 Redis 8.10 的 BACKUP、手工复制、多段 AOF 还是 RDB,恢复验证都应在隔离实例中进行,避免候选文件覆盖仍可使用的正式恢复源。验证至少包括:
- 启动日志显示加载了预期的文件和恢复源。
- 键数量、抽样关键值和 TTL 符合备份时间点的预期。
- 应用能够完成关键数据的读取、写入、删除和过期流程。
- 实际数据损失窗口和恢复耗时满足约定的 RPO、RTO。
- Cluster 环境中的槽位、复制关系和节点数据均已核对。
只有备份文件和恢复步骤都通过这套检查,才算完成一次恢复演练。
7.4 运维操作清单
损坏文件的检查与恢复
如果 Redis 因持久化文件异常而无法加载数据,首先应停止反复重启,并完整保留原文件、Redis 日志和配置。所有检查与修复都应优先针对副本进行。
AOF 异常需要先区分“数据被截断”和“字节格式损坏”:
- 如果 AOF 在尾部发生非预期 EOF,也就是最后一条命令缺少部分字节,在默认
aof-load-truncated yes下,Redis 通常会加载有效前缀、忽略不完整尾部并记录警告。 - Redis 8.10 还提供
aof-load-corrupt-tail-max-size,处理尾部已经写入、但格式不合法的字节。默认值0表示禁止自动恢复;设为非零值后,只有损坏尾部不超过指定字节数时,Redis 才会自动截断并继续启动。它与处理缺失字节的aof-load-truncated不是同一种机制。 - 如果格式损坏发生在文件中部,Redis 会拒绝加载。手工执行
redis-check-aof --fix可能删除从损坏偏移开始直到文件末尾的全部内容,损失范围可能远大于一条命令。
Redis 7+ 检查多段 AOF 时,应把清单文件作为 redis-check-aof 的入口,让工具按清单检查 BASE 和全部 INCR 文件:
# 先对副本执行只读检查
redis-check-aof /path/to/appendonlydir/appendonly.aof.manifest
redis-check-rdb /path/to/dump.rdb
# 仅在确认预计丢失范围后,才对 AOF 副本执行修复
redis-check-aof --fix /path/to/appendonlydir/appendonly.aof.manifest
多段 AOF 的截断修复只允许作用于文件集合中的最后一个 AOF 文件,不能把 --fix 理解为能够任意修复 BASE 或较早的 INCR 文件。BASE 或较早的 INCR 损坏时,应优先从完整备份恢复。
检查与修复都不应直接作用于唯一原件。更稳妥的流程是:
- 停止继续向故障实例写入。
- 复制完整数据目录、配置和日志。
- 在副本上先运行不带
--fix的检查,确认损坏位置和预计丢失范围。 - 将修复结果与其他 RDB、AOF 副本或异机备份比较。
- 在隔离实例中启动恢复副本,核对键数量、关键值和 TTL。
- 业务方确认可接受的数据边界后,再决定是否替换正式恢复源。
谨慎切换持久化策略
在线实例可以执行:
redis-cli CONFIG SET appendonly yes
Redis 会启动后台过程,为当前内存数据建立初始 AOF,并记录后续写入。但安全切换不能止于这条命令:
- 先备份现有 RDB。
- 在线启用 AOF。
- 通过
INFO persistence等待aof_rewrite_in_progress和aof_rewrite_scheduled都变为0。 - 确认
aof_last_bgrewrite_status:ok和aof_last_write_status:ok。 - 将配置写入
redis.conf、容器参数或配置管理系统。 - 在受控环境验证重启后的键数量、抽样数据和 TTL。
- 再安排正式重启。
只修改运行时配置却没有持久化配置,下一次重启会恢复旧配置;只修改配置文件后立即重启,则可能在初始 AOF 尚未建立时产生错误的恢复预期。
7.5 Cluster 的备份边界
Redis Cluster 将不同哈希槽的数据分布到不同主节点,每个节点只持久化自己的本地数据集;副本节点保存的是对应主节点的数据副本。备份一个节点的 RDB,并不等于备份整个集群。
集群备份和恢复还需要考虑:
- 覆盖所有主节点的数据。
- 记录集群拓扑和槽位归属。
- 控制不同节点备份之间的时间偏差。
- 验证恢复后的槽位、复制关系和应用读写。
- 避免把单节点恢复成功误认为全局数据已经一致。
Redis 8.10 的 BACKUP 也需要在各节点独立执行。可以错开不同节点的 BACKUP START,避免多个节点同时 fork(),但逐节点完成和封存的文件集合并不会自动组成全局原子快照。
8. 常见误区与总结
| 误区 | 为什么不成立 |
|---|---|
| 开启 AOF 就不会丢数据 | 数据损失窗口仍取决于 appendfsync、操作系统、文件系统、存储硬件和故障类型。AOF 也无法解决误删除、磁盘损坏或应用写入错误数据。 |
| 有副本就不需要持久化和备份 | Redis 复制通常是异步的,故障切换仍可能损失尚未复制的数据;错误写入也可能迅速传播到副本。副本不是历史备份。 |
| RDB 每次都会复制整个内存 | fork() 后父子进程最初共享内存页,任一方后续写入共享页才会触发 COW。写入密集时仍可能复制大量页面。 |
| AOF 是完整的业务审计日志 | AOF 的目标是恢复 Redis 数据集,AOF 重写会把历史命令压缩为当前状态,不能替代不可篡改的业务审计日志。 |
| 持久化文件存在就代表备份有效 | 文件可能过旧、损坏、权限错误,或者缺少多段 AOF 的一部分。只有在隔离环境中完成实际恢复并核对数据,才能证明备份有恢复价值。 |
本文的七个结论
- 持久化负责重建实例数据,高可用负责故障接管,备份负责跨越本地故障域,三者不能互相替代。
- RDB 通过
fork()和 COW 生成时间点快照,快照周期决定其主要数据损失窗口,写入压力决定额外内存风险。 - AOF 只有在写入经过操作系统并到达符合目标的存储边界后才具有相应耐久性,
appendfsync决定性能与数据窗口的取舍。 - AOF 重写保存当前数据集的等价状态;Redis 7+ 用基础文件、增量文件和清单管理重写期间的写入与原子切换。
- Redis 启动时加载什么取决于版本、配置和实际文件;进程启动成功不等于预期数据已经恢复。
- 持久化方案应按数据价值、RPO、RTO、资源成本和业务可靠性要求选择,而不是套用统一配置。
- 备份只有在跨故障域保存并通过隔离恢复、数据核对和应用验证后,才是一项可信的恢复能力。
真正可靠的持久化不是“磁盘里存在一个文件”,而是:
系统明确知道故障时可能丢失什么、使用哪份数据恢复、需要多长时间,并且已经用真实恢复演练验证过这条路径。