凌晨两点,订单服务重启后监控转绿,客服却发现一笔已支付订单变回了“待支付”。支付回调与数据库流水都在,只有 Redis 中的 order:20260819:1003 回到了较早状态。
一句“Redis 数据丢了”解释不了事故。要继续追问:订单状态为什么只有 Redis 一份?退出前写命令是否完成 fsync?启动时加载的是 RDB 还是 AOF?恢复后有没有与订单库对账?

持久化不是“永不丢数据”的开关。先说清业务愿意丢多少、愿意停多久,再决定用 RDB、AOF,还是两者同时开启。
支付结果、资金余额、不可重复发放的权益等业务事实,不能只存在 Redis。即使使用 appendfsync always,也仍有应用确认边界、磁盘控制器、故障切换和跨系统一致性问题。支付数据库或可靠账本应当是真相源,Redis 可以保存加速读取的状态、幂等标记或短期编排数据,但恢复后必须能从真相源校正。
持久化选型常被写成一张“RDB 快、AOF 安全”的对比表,但生产决策不能停在形容词上。我们需要两个可验证的目标。
RPO(恢复点目标)回答“故障后最多允许回退多久”。如果订单状态的 RPO 是 0,意思不是“把 AOF 调成 always 就完成任务”,而是这条业务链路必须有能确认落地的真相源,并能对 Redis 做重放或对账。如果文章浏览量允许少几十条,RPO 可以是几秒甚至几分钟,持久化压力就可以小很多。
RTO(恢复时间目标)回答“从故障发生到服务恢复,最多能等多久”。一份很新的 AOF 若要重放很久,RPO 可能很好,RTO 却不合格;一份 RDB 可以快速加载,但快照太旧,RTO 合格而 RPO 不合格。两者必须一起看。
下面的决策工具把写入速率、丢失窗口、数据规模与恢复吞吐放在一起。不要把计算结果当成 SLA;它更适合在评审会上追问:“丢掉的这批记录从哪里补回来?恢复时间有没有包含业务校验?”
RDB 可以理解成数据集在某一时刻的定格。执行 BGSAVE 时,Redis 先 fork 出子进程;子进程把自己看到的数据写进临时 RDB 文件,完成后再用原子替换把它变成正式的 dump.rdb。主进程继续处理客户端命令,所以最终快照对应的是 fork 附近的逻辑时间点,不包含随后才发生的写入。
SAVE 也能生成 RDB,但它在保存完成前会阻塞服务线程,通常只适合明确停机的维护过程。生产实例更常用自动 save 规则或 BGSAVE。下面的规则表示 300 秒内至少发生 100 次修改才安排快照,并不是每 300 秒准时执行。
save 300 100
dbfilename dump.rdb
rdbcompression yes
rdbchecksum yes
stop-writes-on-bgsave-error yes很多人看到“后台保存”就以为主进程完全无感。fork 需要建立子进程并复制页表,数据集越大、内存页越多,这一步越可能形成可观察的延迟尖峰。子进程开始工作后,父子进程先共享同一批物理内存页,并不会立刻复制一整份数据集。
真正增加内存的是后续写入。父进程要修改共享页时,操作系统为被修改的页建立副本,这就是写时复制(COW)。因此 COW 的压力取决于保存期间修改了多少内存页,而不是只看“改了多少个 key”。一个很小的字段更新也可能弄脏它所在的内存页;写入越密集、子进程保存越慢,额外内存越高。

下面的模拟器专门观察这个过程。把数据集调大但保持低写入,和保持数据集不变却提高脏页比例,得到的风险并不一样。容量规划时要给 fork/COW 留余量,并在真实写入模型下测量,而不是简单预留“另一份完整数据集”。
RDB 的代价也很直接:两次成功快照之间发生的写入,在机器突然断电时无法从这份 RDB 恢复。保存间隔为五分钟,并不保证“最多只丢五分钟”,因为上一次后台保存可能失败,下一次可能尚未触发。RPO 应按“最后一份已验证、可恢复的快照”计算,而不是按配置文件中的间隔计算。
AOF 记录会改变数据集的命令,启动时通过重放这些操作恢复状态。理解它时要把四个动作分开:Redis 执行写命令,把命令放入 AOF 缓冲区,调用 write 交给操作系统页缓存,再由 fsync 请求把数据推进持久化介质。客户端收到成功回复时走到了哪一步,取决于 appendfsync 策略。

appendfsync always 会在一批写命令追加后执行 fsync,并在持久化动作完成后回复客户端。Redis 会尽量对同时到达的命令做组提交,但每批写入仍受磁盘延迟影响。它能缩小单实例进程或机器故障造成的窗口,却不能替代业务数据库事务,也不能保证故障切换后跨节点、跨系统绝对不丢。
appendfsync everysec 是常见折中。后台线程通常每秒执行一次 fsync,正常故障下要接受大约一秒的写入窗口。磁盘发生长时间抖动时,write 和正在进行的 fsync 可能互相影响,尾延迟仍会升高,所以“后台刷盘”不等于“主线程永不受磁盘影响”。
appendfsync no 不是不写 AOF,而是 Redis 不主动执行周期性 fsync,刷新时机由操作系统控制。某些 Linux 默认行为下可能隔几十秒刷新,但这不是可依赖的上限。内核参数、文件系统、虚拟化层和存储设备都会改变窗口。如果业务只需要缓存,直接关闭持久化通常比“开着 AOF 但假装它很安全”更诚实。
appendonly yes
appendfsync everysec
no-appendfsync-on-rewrite nono-appendfsync-on-rewrite yes 能降低后台保存期间的磁盘竞争,代价是这段时间的耐久性退化到近似 appendfsync no。若必须依赖它才能维持延迟,还要排查存储吞吐、I/O 竞争和重写频率。
下面的时间线可以改变策略、写入速率、磁盘抖动和崩溃位置。观察重点是“已回复客户端”和“已持久化”两条线何时分开。
如果对一个计数器连续执行一万次 INCR,恢复当前值并不需要重放一万条历史。AOF 重写会根据当前内存状态生成一组足以重建数据集的最短操作,从而降低文件体积和恢复成本。它不会在原文件上就地删改,也不会让客户端写入停下来等待整个过程结束。
Redis 7 及以后使用多部分 AOF。appenddirname 目录内最多有一个 BASE 文件,可以有多个 INCR 文件,还有一份 manifest 记录加载顺序。默认名称通常类似:
appendonlydir/
├── appendonly.aof.2.base.rdb
├── appendonly.aof.2.incr.aof
└── appendonly.aof.manifest触发 BGREWRITEAOF 后,子进程生成新的 BASE;父进程立即打开新的 INCR,继续接住重写期间的写命令。新 BASE 完成后,Redis 生成临时 manifest,把新 BASE 与对应 INCR 组合起来,再原子切换 manifest,最后清理不再使用的旧文件。若重写中途失败,旧 BASE、旧 INCR 与新开的 INCR 仍能表示完整状态,下一次可以重试。

appenddirname "appendonlydir"
appendfilename "appendonly.aof"
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-use-rdb-preamble yesaof-use-rdb-preamble yes 容易被误解。它不是“先恢复独立的 dump.rdb,再追加 AOF”这么简单,而是允许 AOF 的 BASE 使用紧凑的 RDB 二进制格式,后续变化仍进入 INCR AOF。恢复时先加载 BASE,再按 manifest 顺序重放增量,既减少重写后的体积,也比纯命令格式的 BASE 更快。这通常被称为混合持久化。
重写同样会 fork,也同样会产生 COW。要同时观察 aof_rewrite_in_progress、aof_last_bgrewrite_status、aof_current_size、aof_base_size 和 aof_last_cow_size。如果 aof_current_size 长期远高于 aof_base_size,重写却一直失败,磁盘空间和恢复时间会一起恶化。
同时开启两种持久化并不表示启动时把两份数据合并。Redis 会优先使用 AOF,因为 AOF 通常比独立 RDB 包含更完整的写入历史。这里有一个危险场景:实例原来只有 RDB,你只修改 redis.conf 中的 appendonly yes,然后直接重启。新实例可能按 AOF 路径启动,却没有完成一份包含现有数据的 AOF 转换。
已有数据从 RDB 切换到 AOF 时,应在运行中的实例先执行配置变更,让 Redis 创建 AOF,并等待重写完成,再把设置持久化到配置文件。核心检查可以这样做:
redis-cli CONFIG SET appendonly yes
redis-cli INFO persistence
redis-cli CONFIG REWRITE等待 aof_rewrite_in_progress:0、aof_rewrite_scheduled:0,确认 aof_last_bgrewrite_status:ok 与 aof_last_write_status:ok,再做受控重启。重启前后不要只比 DBSIZE:key 数相同并不代表订单状态、TTL、Stream 消费进度和业务聚合值都相同。至少抽查关键对象,最好跑一遍业务对账。
下面是一轮 Redis 8.2.2 的最小演练。实例启用 AOF、everysec 与 RDB 格式 BASE,写入订单状态后分别执行 BGSAVE 和 BGREWRITEAOF,再停止并重启进程。
PING => PONG
SET order:20260819:1003 => OK
BGSAVE => Background saving started
BGREWRITEAOF => Background append only file rewriting started
rdb_last_bgsave_status:ok
aof_enabled:1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok
aof_current_size:119
aof_base_size:119
AOF files =>
appendonly.aof.2.base.rdb
appendonly.aof.2.incr.aof
appendonly.aof.manifest
GET after restart => paid
DBSIZE after restart => 1这段输出验证了三件事。第一,后台快照和 AOF 重写都成功,不能只看到命令返回“started”就认为已经落盘。第二,Redis 7+ 的 AOF 是由 manifest 管理的一组文件,备份时不能只复制某个看起来最大的 .aof。第三,重启后应该读取业务对象并核对值,不能把“端口能连”当成恢复完成。
完整演练还要记录停止前最后一个业务序号,在隔离实例加载备份,测量恢复到允许切流的总时长,检查 key、关键 TTL 和抽样值,再与业务数据库对账。这样得到的 RPO/RTO 才来自证据。
持久化故障经常先表现成业务写入报 MISCONF、AOF 状态为 err,或 RDB 后台保存反复失败。此时不要为了让接口先恢复就立即关闭保护开关。先停止故障扩散,保留原文件,再判断是容量、权限、I/O 还是文件内容问题。

先看数据目录与 AOF 目录所在文件系统,而不是只看系统盘。清理目标也不能是当前仍被 manifest 引用的文件。确认 dir、appenddirname、磁盘剩余空间、inode、AOF 增长速度和是否有失败重写留下的压力,再决定扩容或清理无关文件。恢复空间后观察 aof_last_write_status 和后台保存状态是否重新变为 ok。
Redis 进程必须能进入数据目录、创建临时文件、写入并执行原子重命名。只给现有 dump.rdb 写权限仍可能失败,因为后台保存要创建临时文件并替换目标。容器环境还要检查挂载卷的属主、只读标志、安全策略和启动用户。修正权限后用一次受控 BGSAVE 或 BGREWRITEAOF 验证,不能只靠 ls -l 推断。
先停写或隔离实例,把原始文件复制到安全位置,再在副本上检查。AOF 只有末尾命令被截断时,默认配置可能加载可解析部分并丢弃不完整尾部;如果中间出现非法字节,实例通常拒绝启动。redis-check-aof 应先不带 --fix 查看错误位置,确认会丢弃多大范围后才考虑修复。直接执行 --fix 可能从损坏点开始删掉后续内容。
redis-check-aof appendonlydir/appendonly.aof.manifest
redis-check-aof --fix appendonlydir/appendonly.aof.manifest
redis-check-rdb dump.rdb修复完成不代表业务完整。应把修复后的文件加载到隔离实例,核对最后业务序号、关键状态与 TTL,再决定是否切流;若真相源存在,就按缺失区间重放。不要在唯一一份原文件上边试边改。
日常告警至少应覆盖 rdb_last_bgsave_status、aof_last_write_status、aof_last_bgrewrite_status、最近成功保存时间、磁盘可用量、AOF 当前体积与重写基线、fork/COW 指标和磁盘延迟。只监控 Redis 是否能 PING,会让实例在无法持久化的状态下继续运行很久。
主从复制能在节点故障后提供另一份在线副本,却不等于备份。误执行 FLUSHALL、应用 bug 批量覆盖 key、过期策略配置错误时,错误也会被复制到从节点。主节点关闭持久化又自动以空数据重启时,从节点甚至可能跟随同步成空数据。复制减少单节点故障的停机时间;备份提供回到过去某个已知状态的能力,两者解决的问题不同。

RDB 是不可变的时间点文件,生成完成后可以复制,并用时间命名做小时、日、月级保留。至少一份应离开当前机器,重要数据还应离开当前可用区或数据中心。传输要加密,目标存储要限制删除权限,并定期校验文件,而不是把“上传任务返回成功”当作备份可用。
多部分 AOF 的备份必须保持 manifest、BASE 和相关 INCR 的一致组合。如果边重写边随意复制目录,可能拿到互不匹配的一组文件。安全做法是短时禁止自动重写,确认 aof_rewrite_in_progress:0,复制整个 appenddirname,完成后恢复原有重写策略。也可以对这些只追加或整体替换的文件建立一致的硬链接集合,再从硬链接复制,但流程仍要经过演练验证。
备份成功的判定应放在恢复端:能在一台干净实例上加载,能通过文件校验,能读出关键业务对象,能与真相源对账,并且总耗时满足 RTO。只有“恢复成功”才说明这份备份有用。
如果 Redis 只是可随时回源重建的缓存,可以关闭持久化,或者保留低频 RDB 缩短预热;前提是数据库和上游扛得住冷启动回源。如果会话丢失会造成大量用户退出,可以使用 AOF everysec,同时接受极端故障下的小窗口,并准备重新登录与会话失效方案。如果是可从日志重算的计数或榜单,可以根据重放成本在 RDB 与 AOF 之间取舍。
对于丢失代价高、恢复也要快的数据,通常同时保留 AOF 与周期 RDB:AOF 把恢复点拉近,RDB 提供独立备份、快速恢复起点和另一条修复路径。即便如此,支付、资金和订单事实仍应由专门数据库或账本承担,Redis 的持久化负责缩短恢复与补偿距离。
上线前把下面这些问题写进运行手册,并给出负责人和验证日期:
appendfsync、重写阈值、aof-use-rdb-preamble 和数据目录是什么?RDB 与 AOF 的选择最终不是“哪个更高级”。你是在明确承诺:故障发生时,哪些写入可能消失,服务多久能回来,消失的数据从哪里补,以及谁来证明恢复结果可信。能把这四件事说清楚,持久化配置才算真正进入生产设计。