ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Redis持久化全解析:RDB与AOF原理、配置与实战坑位

Redis持久化全解析:RDB与AOF原理、配置与实战坑位 Redis 这种内存数据库最让人纠结的就是数据安全问题。进程一挂内存里的数据说没就没虽然重启很快但缓存穿透和冷启动的代价谁用谁知道。持久化就是给 Redis 的内存数据加一道保险让它在重启后还能把数据找回来。这篇文章会把 RDB 和 AOF 两条路线的原理、命令、配置和实战坑位一次讲透适合刚把 Redis 跑起来、正琢磨怎么配置持久化的开发也适合被线上数据丢失问题折磨过的运维同学。1. 持久化的整体设计思路为什么是 RDB 和 AOF 两套方案先说一个很多人没想通的问题Redis 是内存数据库数据本来就该活在内存里为什么非要关心持久化答案是 Redis 除了当缓存还经常被当作真正的数据库用比如存会话、存计数、存业务配置。一旦实例重启或者宕机内存数据全部清空如果上层没有兜底业务就等于裸奔。所以持久化不是 Redis 的附加功能而是它在很多场景下能够被信任的基础。Redis 官方给了两种持久化手段RDB 和 AOF。它们解决问题的角度完全不同。RDB 是定期拍照把某一时刻内存里的全量数据压缩后写进一个二进制文件恢复的时候直接加载这个快照。AOF 是记账本把每一条写命令追加到日志文件里恢复的时候重放这些命令把数据重新构建出来。一个讲究快照的速度和恢复效率一个讲究记录的完整性和数据安全。这两套方案不是谁替代谁的关系而是互补关系。RDB 恢复快但快照之间存在丢失窗口AOF 记录密但文件会越来越大恢复速度也慢。Redis 4.0 之后引入的混合持久化又把两者的优势捏合在一起用 RDB 格式存全量快照用 AOF 格式存增量命令兼顾了快速启动和低数据丢失。这个演进过程挺值得琢磨的理解了每套方案的出发点后面配置起来才有底气而不是抄一份配置了事。1.1 RDB 和 AOF 的定位差异RDB 的核心设计思想是空间换时间。它把整个数据集压缩成一份紧凑的二进制文件结构简单加载快非常适合做全量备份、主从复制的初始同步。但它有一个天然的软肋两次快照之间的数据变更不会被记录。如果你的 Redis 每分钟才做一次快照那最后一次快照之后的一分钟内写入的数据在宕机时就全丢了。这个丢失窗口有多长取决于你的快照频率。AOF 则是时间换空间的思路。它记录的是每一条写命令理论上只要日志完整恢复出来的数据就能精确到最后一次写入。代价是 AOF 文件增长极快恢复时要一条条重放命令速度明显慢于 RDB。而且如果记录得太细每个写操作都落盘性能损耗会非常明显。所以 AOF 配置的核心矛盾就是数据安全性和性能之间的取舍。实际操作中很多人喜欢问到底选 RDB 还是选 AOF这种问法本身就把问题简化了。线上实例大多两个都开着RDB 负责快速恢复和备份AOF 负责补齐快照之间的数据。Redis 重启时会优先加载 AOF因为它的数据更新。如果 AOF 关了才会去加载 RDB。1.2 持久化在三种典型场景下的选择策略不是所有场景都需要相同的持久化策略这得分情况看。纯缓存场景数据丢了也无所谓甚至清空缓存是件好事。如果 Redis 里全是可再生的数据比如热点商品信息、用户登录 token持久化可以完全关闭省掉写盘开销还能避免 fork 带来的延迟。这种场景追求的是极致性能不是数据安全。混合读写场景就麻烦一些。比如用 Redis 存用户购物车丢了客户会投诉。这种场景必须打开 AOF而且 appendfsync 至少要配成 everysec保证最多丢一秒的数据。同时 RDB 也要开着作为周期性备份万一 AOF 文件写坏了还有一份保底的快照。这是我见过最稳妥的配置组合。还有一种场景容易被忽略——Redis 作为主从架构中的主节点。主节点在做全量同步时会把 RDB 快照发给从节点如果主节点没开 RDB从节点就无快可同步。所以只要你有从节点RDB 就是刚需。主从配合持久化能实现数据多副本 多持久化的双保险。2. RDB 持久化快照原理与相关命令实操RDB 是 Redis 持久化的元老方案从 Redis 1.0 就有了。它的实现逻辑听起来简单但里面藏着不少性能调优的细节尤其是涉及 fork 和写时复制光是快照会不会阻塞主进程这个问题就能聊出很多门道。2.1 SAVE 和 BGSAVE同步落盘与异步落盘的取舍RDB 的生成有两种触发方式对应两条命令SAVE 和 BGSAVE。SAVE 是同步操作执行它的时候 Redis 主进程会直接阻塞整个实例停下来把内存数据序列化并写入磁盘期间不处理任何请求。这种方式的唯一优点是实现简单几乎没有额外开销但缺点致命生产环境基本不会手动去执行。我见过有人直接敲 SAVE 把 Redis 卡住了几十秒所有请求超时那种场面很尴尬。BGSAVE 是正牌方案。它会 fork 出一个子进程子进程负责生成 RDB 文件父进程继续处理请求。听起来完美但 fork 本身是有代价的需要拷贝父进程的内存页表。如果 Redis 的内存占用很大fork 阶段会有明显的阻塞这个时间通常几十毫秒到一两秒不等取决于实例内存量和服务器 CPU 性能。所以BGSAVE 不阻塞是相对而言的它不阻塞落盘过程但阻塞在 fork 环节上。手动备份的时候优先用 BGSAVE。执行后可以用LASTSAVE命令确认最近一次成功快照的时间戳如果返回的时间是执行前的时间说明快照可能没触发需要排查原因。2.2 自动触发快照的机制与配置参数RDB 的自动触发靠的是配置项save格式是一组秒数 变更次数的二元组。只要满足距离上次快照超过 N 秒且期间发生了至少 M 次写操作Redis 就会自动执行 BGSAVE。比如配置里常见的save 900 1 save 300 10 save 60 10000这组配置的意思是900 秒内至少有 1 次写操作就触发快照300 秒内至少有 10 次写操作就触发快照60 秒内至少有 10000 次写操作就触发快照。按条件命中最快的那个来执行也就是说如果 60 秒内疯狂写入超过一万次不需要等 300 秒的阈值直接快照。这里有个容易误会的点save配置里没有参数的情况下Redis 默认是启用 RDB 的。如果所有save行都被注释掉RDB 就关闭了。有些人以为关掉 AOF 就能让 Redis 变快结果 RDB 的自动快照还在跑磁盘天天被打满查了半天才发现是老的 save 配置没清理。除了时间阈值还有两个场景会触发自动快照一是主从复制场景中从节点连接主节点发起全量同步主节点会自动生成快照发给从节点二是 Redis 正常关闭时如果配置了 RDB会生成一份全量快照再退出。所以想临时关掉 RDB光改配置不够还得把运行时配置用CONFIG SET同步关掉。2.3 RDB 文件的恢复流程和校验方法RDB 文件的默认名称是 dump.rdb默认放在 Redis 启动目录下。恢复过程很简单把备份文件放到配置指定的dir目录下改好dbfilename名称启动 Redis 时会自动加载。加载成功后日志里会有一条类似于 DB loaded from disk 的记录说明恢复完成。但实际恢复数据的时候有几个坑。第一个坑是版本兼容问题新版 Redis 生成的 RDB 文件里带了版本号老版本 Redis 不认识新格式会拒绝加载。比如 Redis 7 的 RDB 文件放到 Redis 5 里直接报错。升级 Redis 前一定要想好持久化文件的兼容问题。第二个坑是 RDB 文件损坏文件下载到一半或者磁盘写满导致写入不完整Redis 启动时会直接失败。这种情况下不要急着找备份先用 Redis 自带的工具校验redis-check-rdb dump.rdb这个工具会逐字节检查文件结构报告出具体的错误点。如果文件真的损坏了只能从最近的可用备份恢复这也是为什么强调 RDB 备份文件一定要额外拷贝到其他机器上的原因。2.4 快照对性能的影响与写时复制原理快照对性能的影响主要不在写盘而在 fork 那一刻的内存复制。Linux 的 fork 通过写时复制机制让子进程和父进程共享同一份物理内存只有在父进程后续写入某个内存页时才会真正复制一份副本给子进程。所以子进程生成 RDB 文件时看到的是 fork 那一瞬间的内存快照后续父进程的写操作不会影响快照内容这就是快照二字的由来。这个设计很聪明但副作用也有。如果 Redis 在快照期间写入量大父进程会持续触发内存页的复制内存占用会瞬间翻倍。我在一台内存紧张的机器上遇到过这样的情况Redis 占用 4GB 内存BGSAVE 期间内存直接飙到 7GB 多差点触发 OOM。这种场景下合理的做法是把 RDB 的快照频率调低或者配置系统层面预留足够的内存余量。另外fork 的耗时和实例的 key 数量强相关key 越多页表越大fork 越慢。监控里如果看到 fork 耗时飙升多半是 key 数量超过了正常水平。3. AOF 持久化命令日志的精妙设计与落地细节AOF 本质上是一条条写命令的追加日志。它不像 RDB 那样定期整体写快照而是每执行一条写命令就把这个命令以 Redis 协议格式写进日志文件。恢复的时候Redis 一条一条地重新执行这些命令就能把数据还原出来。这个思路简单直接但把细节做好比想象中复杂得多。3.1 写命令记录机制与缓冲区设计AOF 的写入路径不是直接落盘而是经历了两个阶段命令先被写入 AOF 缓冲区再由缓冲区写入内核的页缓存最后通过 fsync 刷到磁盘。为什么多一层缓冲区因为 Redis 是单线程模型如果每条命令都同步写盘性能会急剧下降。缓冲区的存在让 Redis 可以把多次写操作批量提交提高写入吞吐量。这个设计也带来了风险。如果进程在命令写入缓冲区之后、fsync 落盘之前崩溃这部分命令就丢了。所以 AOF 提供了appendfsync配置决定落盘的时机这其实是性能和安全性之间的一个旋钮。理解了这个流程再看appendfsync的三个取值就清晰了配置取值实际行为数据丢失风险性能影响always每写一条命令就立即 fsync只丢正在写但未完成的那条命令几乎为零最差每条写命令都要等到磁盘确认everysec每秒批量 fsync 一次最多丢 1 秒内的写入数据较好仍是大多数场景的首选no交给操作系统决定落盘时机可能丢几十秒甚至更久的数据最好依赖 OS 的刷盘策略默认配置是 everysec这也是我在生产环境中的推荐选择。always 看似完美但在 SSD 和机械硬盘上都会明显降低写入性能除非是资金交易类场景否则不建议用。no 的性能最好但数据丢失的窗口太大一旦宕机损失不可控生产上基本没人这么配。3.2 AOF 重写机制与 BGREWRITEAOF 命令AOF 文件会随着写命令的累积无限膨胀。一个 key 被反复修改 100 次AOF 文件里就会留下 100 条记录但最终只需要一条结果。为了控制文件大小Redis 设计了 AOF 重写机制生成一个新的 AOF 文件只包含重建当前数据集所需的最少命令。手动触发重写用 BGREWRITEAOF 命令BGREWRITEAOF执行过程和 BGSAVE 类似也是 fork 子进程新的 AOF 文件在子进程中构建构建完成后原子替换旧文件。自动触发则由两个参数控制auto-aof-rewrite-percentage和auto-aof-rewrite-min-size。默认配置是文件大小超过上一次重写后大小的 100%且文件大于 64MB 时自动触发重写。这里有一个非常关键的实现细节AOF 重写期间Redis 会同时把期间的增量写命令记录到一个缓冲区里重写完成后追加到新文件尾部保证重写后的 AOF 文件包含的是最新数据。如果不理解这个机制很容易在重写过程中看到 AOF 文件大小不减反增以为出了 bug。其实那是新旧文件交替期间出现的临时现象。3.3 AOF 文件的加载、修复与安全策略Redis 启动时如果检测到 AOF 文件存在会优先加载 AOF 文件恢复数据。加载过程中会逐条执行记录的命令所以 AOF 文件大的时候启动时间会明显比 RDB 长。如果 AOF 文件因为异常写入损坏了Redis 启动时会拒绝加载并提示用户修复。用自带工具可以修复redis-check-aof --fix appendonly.aof这个命令会扫描文件把不符合协议格式的尾部内容截掉尽量找回可用的命令。但要注意修复过程会丢失损坏之后的尾部数据所以修复后的文件不是完整的只能算尽力而为。更稳妥的做法是通过主从节点或者定时备份把数据副本保存到其他位置而不是依赖单机文件的自然健壮性。AOF 文件格式本身是纯文本的 Redis 协议比如*3\r\n$3\r\nSET\r\n...这样的结构因为协议里包含了完整的命令语句所以才能被重放执行。这也是为什么有人说看 AOF 文件可以反向追踪数据变更历史在排查某些诡异的数据问题时AOF 里的记录顺序确实能帮上忙。3.4 混合持久化RDB 全量 AOF 增量的组合方案AOF 重写虽然能压缩文件体积但它的缺点是重写后依然是命令序列恢复时还需要逐条执行速度远不如直接加载 RDB 快照。Redis 4.0 引入的混合持久化解决了这个问题。开启混合持久化只需要一个配置项aof-use-rdb-preamble yes开启后AOF 重写生成的日志文件前半段是 RDB 格式的全量数据快照后半段是重写期间积累的增量写命令。加载时 Redis 先快速载入 RDB 格式部分再用命令补上增量既享受了 RDB 的加载速度又保留了 AOF 的完整记录窗口。这个方案在 Redis 4.0 之后的版本里很成熟成为线上实例的标配也没问题。不过有一个兼容性问题要注意开启混合持久化后生成的 AOF 文件Redis 4.0 之前的版本无法识别。如果存在需要回退版本的情况务必先关闭混合持久化拿到纯 AOF 格式的文件再操作。4. 实践经验选型建议、常见坑位与排查速查表持久化方案的配置在测试环境里调来调去很容易但真正上线后遇到的问题往往在文档里找不到答案。我把这几年实战里踩过的坑和总结的经验集中写出来能帮你少走不少弯路。4.1 线上持久化配置的推荐组合与依据我的固定配置思路是RDB 保底 AOF 保最新 混合持久化提速。具体来说RDB 的save配置保留一个低频兜底比如save 900 1确保持久备份不会缺位AOF 开启并设置appendfsync everysec保证最多丢 1 秒数据再开启aof-use-rdb-preamble yes让重启恢复速度维持在秒级。这套组合用一句话概括RDB 负责快速恢复和全量备份AOF 负责补上快照窗口的增量数据混合持久化让 AOF 文件变得加载更快。如果你的业务对数据丢失零容忍可以把 appendfsync 改成 always但一定要用性能测试数据来衡量代价——同样的写并发下always 可能比 everysec 多损耗 30% 以上的性能这在压测报告中体现得非常明显。4.2 持久化场景下的性能排查与优化技巧持久化导致的性能问题通常集中在两个地方fork 耗时和磁盘 IO 压力。fork 耗时和内存使用量强相关。如果你发现 BGSAVE 或 BGREWRITEAOF 期间实例响应变慢先用INFO stats查看latest_fork_usec字段这个值记录了最近一次 fork 的耗时微秒数。如果这个值持续变大考虑优化方向降低实例内存占用、拆分大实例为多个小实例、减少 key 数量或者升级 CPU。不要指望通过增加内存来解决fork 耗时和内存量成正比内存越大页表越大fork 越慢。磁盘 IO 压力的问题更隐蔽。当 AOF 和 RDB 同时开启时BGSAVE 和 AOF 重写可能撞在一起导致短时间内大量磁盘写操作。虽然 Redis 会做一定程度的互斥但高负载下还是可能出现写竞争。一个有用的监控指标是INFO persistence里的rdb_bgsave_in_progress和aof_rewrite_in_progress如果两者频繁同时为 1说明磁盘压力大要考虑错开触发时机或者换个更快的存储介质。还有一个我在生产环境遇到的怪问题系统内存充足但持久化总是慢悠悠的。排查到最后发现是 Linux 系统页缓存压力过大导致 fsync 被阻塞。这提醒我们持久化不只是 Redis 自己的事操作系统层面的 IO 调度、磁盘剩余空间、内存压力同样会影响结果。4.3 高频问题排查速查表很多持久化问题反复出现我把常见现象、直接原因和解决思路整理成一张表排查的时候可以直接对照现象可能原因排查命令与解决方向Redis 启动失败但不报错RDB/AOF 文件损坏用 redis-check-rdb / redis-check-aof 校验并修复BGSAVE 过程中响应变慢fork 受内存页表大小影响INFO stats 查看 latest_fork_usec拆分大 keyAOF 文件增长过快重写触发条件设置过高调小 auto-aof-rewrite-percentage重启后数据不完整appendfsync 配置为 everysec / no评估数据丢失容忍度调整刷盘策略恢复速度极慢AOF 文件过大且未开启混合持久化开启 aof-use-rdb-preamble考虑 RDB 先行AOF 文件不断变大却不重写rewrite 条件一直不满足手动执行 BGREWRITEAOF观察触发阈值重启后 AOF 和 RDB 数据不一致单一存储模式配置混乱以 AOF 为准检查配置项是否两边都开着持久化文件占满磁盘备份文件未定期清理配置日志轮替和备份保留策略这张表不是银弹但覆盖了我见过的高频问题。需要强调的是无论配置怎么调定期把 RDB 文件做异地备份永远值得做。有些问题不是重启能解决的比如服务器整个硬盘坏了这时候异地备份才是唯一的救命稻草。4.4 我对持久化方案选型的一点体会持久化的配置组合没有标准答案本质上是在性能和安全性之间做权衡。我见过把 AOF 关掉只留 RDB 的团队理由是缓存丢了就丢了也见过对 everysec 都不放心、坚持用 always 的金融项目各有各的道理。关键是搞清楚两点你的数据丢了会有什么后果你愿意为这份安全付出多少性能代价。个人建议是哪怕你的 Redis 只用来做缓存也至少把 RDB 开着成本很低但能让你在故障复盘时多一个工具可用。数据安全不是上线后才考虑的而是架构设计时就该想清楚的事情。另外还有一个小技巧在做 Redis 版本升级前先停掉实例手动执行一次BGSAVE和BGREWRITEAOF确保持久化文件都是当前版本生成的。然后备份这两个文件到独立目录再开始升级。这样做能在升级出问题时快速回滚而不用依赖旧服务器上的运行时数据这个习惯帮我避开过两次线上事故。最后想分享的是别等到 Redis 宕机了才翻开持久化文档。花几分钟把当前的持久化配置、备份位置和恢复步骤写进团队文档关键时刻真的能救命。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进