Redis 把数据放在内存(5.1),代价就是断电即失——持久化机制就是给这个代价打的补丁。本节拆两种互补的持久化方式:RDB 快照与 AOF 日志,各自的实现原理、丢数据窗口与性能代价,最后给出一套按业务容忍度选择的决策流程。第 4.5 节 MongoDB 的写关注讨论在这里有镜像:Redis 同样要回答"确认到什么程度才算安全"。
RDB 把某一时刻的完整数据集写成紧凑的二进制文件。实现核心是 fork + 写时复制(COW):主进程 fork 出子进程,子进程遍历内存生成快照文件;fork 后父子进程共享内存页,只有被修改的页才复制副本——主进程继续服务、子进程安心写盘,几乎不互相干扰。
这个机制的三个推论决定了 RDB 的性格。快照是时间点:两次快照之间的写入不落在任何 RDB 里,宕机时丢的就是这段窗口(分钟级,取决于快照间隔)。fork 有瞬间成本:内存越大 fork 越慢(复制页表),且 COW 期间写入会放大内存占用,超大实例 + 大促写入高峰是 RDB 的危险组合。恢复快:二进制全量文件直接载入,百万键级也是秒到分钟级——比 AOF 重放快得多。
AOF 把每条写命令追加到日志文件,重启时重放全部命令恢复数据。写入策略由参数控制,三档对应三种丢数据窗口:
| 追加策略 | 语义 | 丢失窗口 | 性能 |
|---|---|---|---|
| always | 每命令刷盘 | 近乎零 | 差(每写一次磁盘 IO) |
| everysec(默认) | 每秒刷盘 | 约 1 秒 | 好 |
| no | 交操作系统 | 不确定 | 最好,不可控 |
AOF 的膨胀问题由重写解决:AOF 文件里同一键被改了一万次就有一万条命令,重写进程按当前内存状态生成"每个键一条命令"的最小日志,体积骤降。4.0 起支持混合持久化:重写后 AOF 前半段是 RDB 格式的全量数据、后半段是增量命令——恢复速度接近 RDB、丢数据窗口维持秒级,两全其美,成为当前主流默认。
背景:订单状态服务用 Redis 存处理中状态(准生产配置:RDB 每小时一快 + AOF everysec),某日凌晨宿主机断电,Redis 进程未及优雅关闭。
操作:推演恢复过程。第一步重启实例,加载 AOF(含最后一次重写的 RDB 头 + 增量命令),最终恢复到断电前约 0.8 秒的状态;第二步盘点损失——断电前 1 秒内约 40 条状态更新丢失,其中 12 个订单的状态机停在中间态;第三步补偿——订单服务发现状态缺失的订单回到上游重发,状态机按幂等键去重;第四步决策升级——该业务对丢失容忍度为零,混合持久化 + 每秒刷盘仍不够,把关键状态的双写从 Redis 改为直接写 MongoDB(4.8 流水账模式),Redis 降级为纯加速层。
结果:服务 4 分钟内恢复;数据层面因架构调整,Redis 从此可随时重建,不再承担唯一事实源角色。
解读:这次复盘的价值在最后一步——持久化配置再极致也不能把 Redis 变成主存储:fork 风险、刷盘窗口、重写期间的性能抖动都是内存数据库的先天属性。正确架构是"主存储可重建 Redis",Redis 只承加速。丢 1 秒数据的配置在缓存场景毫无问题,在事实源场景就是事故,配置选择必须先问角色。
变式:若业务是会话类(丢了重新登录即可),RDB 单独就够(每小时快照 + 热备);若是要统计口径的计数类,everysec 的 AOF 合适——配置跟着丢数据容忍度走。
第一个是在高峰期手动触发全量快照或重写:fork 的页表复制与 COW 内存放大叠加写入高峰,实例卡顿甚至内存溢出;重活安排在低谷期。第二个是只配持久化不配主从:单机持久化保护不了主机硬件故障,持久化 + 主从(5.7)才是完整方案,且从库重放的是主库同步的操作流,二者互补。第三个是忽视 AOF 重写期间的写入放大:重写时主进程的写命令同时写 AOF 缓冲与重写缓冲,极端写入下内存翻倍,监控内存要留余量。第四个是误信"开了持久化就不丢":always 档也有进程崩溃时缓冲未刷的毫秒级窗口,任何承诺都要按丢失窗口换算。
| 方案 | 原理 | 数据安全性 | 对性能的影响 | 恢复速度 |
|---|---|---|---|---|
| RDB 快照 | 定时把内存数据整体落盘 | 丢最后一次快照之后的写入 | 子进程 fork 时有短暂停顿 | 快,直接加载 |
| AOF 日志 | 记录每条写命令,重启时重放 | 取决于刷盘策略(通常丢 1 秒) | 写放大,文件持续增长 | 慢,要重放命令 |
| 混合持久化 | AOF 文件里包含 RDB 格式的全量 + 增量命令 | 同 AOF | 介于两者之间 | 快于纯 AOF |
# RDB:900 秒内至少 1 次写入则触发快照;可配置多组条件 save 900 1 save 300 10 save 60 10000 # AOF:开启后每条写命令追加到日志;刷盘策略三选一 appendonly yes appendfsync everysec # 每秒刷盘(默认,折中);always 最安全最慢;no 最快最不安全 # 混合持久化(4.0 起):AOF 重写时以 RDB 格式写前半段,兼顾速度与体积 aof-use-rdb-preamble yes
刷盘策略的选择是唯一需要认真决策的地方:always 每条命令都刷盘,几乎不丢数据但吞吐明显下降;everysec 每秒刷盘,最多丢一秒,是绝大多数业务的选择;no 交给操作系统决定,最快但不可控。
场景:执行了 FLUSHDB,需要把某个业务的数据恢复到误操作前。
# 1) 立即停止写入,防止 AOF 继续追加(否则重写会覆盖掉恢复机会) redis-cli SHUTDOWN NOSAVE # 若只开了 RDB,千万别用带保存的关闭方式 # 2) 找到最近的 RDB 快照或 AOF 文件,复制到安全的临时目录 cp /var/lib/redis/dump.rdb /data/rescue/dump.rdb # 3) 在另一台机器上用这份文件启动一个临时实例(端口与生产隔离) redis-server --port 6399 --dir /data/rescue --dbfilename dump.rdb # 4) 从临时实例导出需要的 Key,核对后回灌生产 redis-cli -p 6399 --scan --pattern 'order:*' | head -1000 > keys.txt # 逐 Key 用 DUMP + RESTORE 迁移(RESTORE 可指定目标实例)
三条血的教训:
数据保住了,下一节进入生产 Redis 的主战场:缓存怎么用才不出事故。