1. 先说结论:到底什么是 Redis 持久化
如果你用过 Redis,大概率遇到过这种场景:业务跑得好好的,数据也在内存里躺着,结果某天服务器重启,或者 Redis 进程意外挂了,重启之后发现——数据丢了不少,甚至全没了。很多第一次接触 Redis 的人会惊呼“Redis 不是数据库吗?数据怎么还会丢”。这里要澄清一个关键认知:Redis 定位是一个基于内存的键值存储系统,默认情况下它的所有数据都只存在内存里。内存的读写速度极快、性能极好,但有个致命的弱点——断电或者进程退出,数据就烟消云散。
持久化机制就是为了弥补这个短板而存在的。所谓持久化,就是定期或者实时地把内存里的数据写入磁盘文件。这样即使 Redis 进程崩溃、服务器断电,重启之后也能从磁盘文件里把数据恢复回来。理解了这一点,你才能真正明白 RDB 和 AOF 这两个机制到底在解决什么问题。
在 Redis 的整个知识体系里,持久化属于“保命”级别的基础能力。无论是自己搭单机环境做项目,还是维护一套生产环境的 Redis 集群,持久化策略的选型和配置直接决定了数据的可靠性。我等下会把 RDB 和 AOF 两种机制从原理、触发方式、优缺点、数据恢复、踩坑经验到生产环境的选型策略完整讲一遍,尽量让你读完就能用上。
2. RDB 快照持久化:把内存“拍照”存下来
RDB 的全称是 Redis DataBase,它做的事情可以用一个很直观的词概括——快照。就像你给内存里的整个数据集拍了一张照片,然后把这个照片以二进制文件的形式存到磁盘上。一旦需要恢复数据,直接把这张照片重新加载回内存就行。
2.1 RDB 的触发方式与配置细节
RDB 的触发主要分三种:手动触发、自动触发、以及关闭持久化时的一些边界情况。我先说配置,再说原理。
你打开 redis.conf 文件,里面有一块经典的配置,长这样:
save 900 1 save 300 10 save 60 10000这三行配置的含义是:
- 900 秒(15 分钟)内至少有 1 次写操作,触发一次 RDB 快照
- 300 秒(5 分钟)内至少有 10 次写操作,触发一次
- 60 秒(1 分钟)内至少有 10000 次写操作,触发一次
这里很多新手会理解错,我特别强调一下:这个规则不是“每隔 900 秒就存一次”,而是“在最近 900 秒这个时间窗口内,如果写操作次数累计达到阈值,就触发一次”。如果你把 save 配置全部注释掉,Redis 就完全不做 RDB 持久化——注意,在没有任何其他持久化机制的情况下,这意味着 Redis 只使用内存、不落盘,重启即丢失所有数据。
手动触发 RDB 有两个命令:SAVE和BGSAVE。区别非常大。SAVE是同步操作,Redis 主进程会阻塞在快照生成上,期间所有客户端请求都无法处理。BGSAVE是异步操作,Redis fork 出一个子进程去执行快照写入,主进程继续对外提供服务。生产环境你基本只需要用BGSAVE。
还有一个容易忽略的触发点:当 Redis 通过SHUTDOWN命令正常关闭时,如果配置了 RDB,它会自动执行一次 RDB 持久化,然后再退出。这意味着正常关机能保住数据,但如果直接kill -9强杀进程,那就只能根据最近一次自动触发或手动触发的 RDB 文件来恢复了。
2.2 fork 背后的写时复制(COW)机制
我刚才提到 fork 子进程来生成快照,这里面的核心机制值得展开说,因为它是 RDB 性能表现的根基。
Redis 执行BGSAVE时,主进程会调用系统fork()创建一个子进程。fork 之后的瞬间,子进程和父进程共享同一份内存数据。此时如果父进程的写操作不多,子进程直接读取共享内存并写入临时 RDB 文件就够了。但如果有新的写请求进来,主进程会利用操作系统的**写时复制(Copy-On-Write,COW)**技术,把即将被修改的内存页复制一份出来,再在副本上做修改。也就是说,子进程看到的内存快照始终停留在 fork 那一刻的状态,不会被后续写操作污染。
这套机制保证了 RDB 生成期间主进程几乎不阻塞,因为最耗时的磁盘写入都发生在子进程里。但代价是,fork 本身会复制进程页表,在大内存实例上 fork 会短暂阻塞主进程,时间通常在几十毫秒到一两秒不等。另外,在写操作频繁的场景下,COW 会导致内存中共享页被大量复制,内存占用会瞬时上涨。如果你的机器内存已经很紧张,BGSAVE可能直接把内存顶爆,触发系统 OOM——这是个非常隐蔽的坑。
2.3 RDB 文件长什么样,怎么恢复
RDB 文件是一个二进制文件,默认名字叫dump.rdb。它的结构主要由这几部分组成:
- 文件头:包含魔数 "REDIS" 和版本号
- 元数据:RDB 版本、Redis 版本、创建时间等
- 数据集:逐条存储键值对,包含类型编码(字符串、列表、哈希等)、过期时间等信息
- 文件尾:校验和,用于检测文件完整性
恢复过程不需要你手动干预。Redis 启动时会自动查找dir配置的目录下的dbfilename文件,找到就加载。加载完成后你就能正常使用了。
顺带说一个细节:如果 redis.conf 里同时开启了 AOF,Redis 启动时会优先加载 AOF 文件,而不是 RDB 文件,因为 AOF 在数据可靠性方面通常更优。只有当 AOF 关闭时,才会去加载 RDB 文件。这个优先级关系在生产环境出问题时非常重要,稍后我会专门讲。
2.4 RDB 的优点和硬伤
RDB 最大的优点,一个是恢复速度快。RDB 文件是二进制紧凑格式,Redis 加载时几乎是按字节解析直接重建数据结构,比如在 4GB 的数据集上,恢复时间通常只需要几十秒量级,而同样数据量用 AOF 重放可能需要几分钟甚至更久。
第二个优点是文件体积小。RDB 保存的是某个时间点的内存快照,多个键可能被压缩存储,文件远小于攒了很久日志的 AOF 文件,方便做备份、上传到对象存储、下载到本地分析。
RDB 的硬伤也很明显:它无法做到数据的不丢失。因为快照是周期性的,两次快照之间的写操作全部丢失。比如你配置的是save 300 10,那么最坏情况下,一场宕机会丢失最近 5 分钟的全部写入数据。对很多业务来说,5 分钟的数据丢失是完全不能接受的——用户下了订单、支付了款项,结果 Redis 一重启订单没了,这种后果可以说非常严重。
3. AOF 日志持久化:把每次写操作都记下来
如果说 RDB 是“结果导向”——直接存结果,那么 AOF(Append Only File)就是“过程导向”——把每一步写操作都记录下来。AOF 文件里存的是 Redis 协议的文本命令,比如SET user:10001 "Alice"。恢复的时候,把 AOF 文件里的命令从头到尾重放一遍,数据就回来了。
3.1 AOF 开启与三个刷盘策略
AOF 默认是关闭的,需要在 redis.conf 里手动开启:
appendonly yes appendfilename "appendonly.aof"这是基本配置。真正影响数据可靠性的,是下面这个参数——appendfsync,它控制日志内容写入磁盘的时机。有三个可选值:
always:每条命令执行完成后,立刻执行 fsync 强制刷盘。最安全,一条数据都不会丢,但性能最差,因为每次写操作都要等磁盘落盘everysec:每秒执行一次 fsync。最多丢失最近 1 秒的写入,性能和可靠性达到不错的平衡no:完全交给操作系统决定何时刷盘。性能最好,但操作系统缓存强制刷盘前的任何数据丢了也就丢了,不可控性强
生产环境默认建议用everysec。always只适合对每条数据都极其敏感、且写入量不大的场景,比如钱包余额之类的。说实话,别轻易用always,它会让你发现 Redis 的性能可能反而比不上普通关系型数据库。
3.2 AOF 重写:不控制文件大小会炸掉
如果你开启了 AOF,又长时间不干预,日志文件会越来越大。比如你把一个值从 1 改成 2、从 2 改成 3、从 3 又改成 100,AOF 文件会存三条SET命令。恢复时执行三条,最终得到 100。但显然,前两条是多余的,因为最终结果只需要SET key 100这一条命令。
为了解决文件膨胀问题,Redis 设计了AOF 重写(rewrite)机制。重写并不是把日志文件变小那么简单,它的逻辑是:扫描当前内存中的完整数据集,重新生成一套最少命令集合,写到一个新的 AOF 文件里,然后替换旧文件。比如刚才的例子,重写后只会有一条SET key 100。
重写有两个触发条件,配置如下:
auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb含义是:当 AOF 文件的体积比上次重写后的体积至少增长 100%(即翻倍)时,并且 AOF 文件大于 64MB,触发自动重写。这里的“上次重写后的体积”,如果从未重写过,则指 AOF 启动时的体积。这两个条件要同时满足才会触发,单独满足一个没有用。
重写的过程也值得一提。Redis 会 fork 一个子进程来做重写,子进程扫描内存生成新的 AOF 内容。这个过程中如果有新的写命令进来,主进程会把新命令同时写入一个重写缓冲区(rewrite buffer),待子进程完成后,再把缓冲区里积累的命令追加到新文件的末尾,保证新文件数据完整。执行BGREWRITEAOF命令可以手动触发重写。
3.3 AOF 损坏与修复工具
AOF 是文本文件,理论上比 RDB 二进制文件更容易出现损坏问题——比如磁盘写了一半、空间不足导致截断。Redis 自带一个修复工具redis-check-aof,使用方式很直接:
redis-check-aof --fix appendonly.aof它会把文件中损坏的尾部命令删掉,尽量保留完整的命令。但这个操作有个不得不说的副作用:修复完成后,被截断掉的那部分命令对应的数据会永久丢失。所以建议修复前先备份原始 AOF 文件,万一丢了重要数据还能再想办法。
另外,AOF 加载数据时会逐条解析命令并执行。如果文件里有语法错误,Redis 启动阶段会直接报错拒绝启动。此时可以先启动修复工具,再用修复后的文件启动。有些教程会让你直接把出问题的那条命令删掉再重启,这在测试环境可以,生产环境务必先备份。
3.4 AOF 的优点和缺点
AOF 最大的优点是数据更加安全。配置everysec最多丢一秒数据,配置always一条不丢。对绝大多数业务来说,这个可靠性已经足够了。另一个隐形优点是,AOF 是可读文本,你可以直接打开文件看到所有写操作记录,便于排查问题,甚至可以用grep之类的方式快速查询某些键的操作轨迹。
它的缺点同样明显。首先是恢复速度慢。AOF 文件是文本协议,恢复时要逐条执行命令,远没有加载二进制 RDB 快照快。假设你已经攒了好几百 MB 的 AOF 文件,Redis 启动可能要耗时好几分钟,这段时间内服务完全不可用。
其次是文件体积大。即使有重写机制兜底,AOF 文件通常还是会比 RDB 大不少,因为日志记录的是操作过程,并且不会有 RDB 那种紧凑的二进制编码。
还有一个很多人没注意到的细节:AOF 开启时,RDB 仍然可以作为冷备文件存在。也就是说,它们不是互斥关系,你完全可以两个都开。下一节我会展开讲。
4. 全方位对比:一张表看懂 RDB 和 AOF
对比这两个机制,不能只看表面区别,得从底层原理、性能、可靠性、恢复速度、文件格式、适用场景等多维度逐个拆。我先给出一张总览表,然后逐条解析背后的逻辑。
| 对比维度 | RDB 快照 | AOF 日志 |
|---|---|---|
| 文件格式 | 二进制压缩快照 | 文本协议命令 |
| 数据恢复速度 | 快,直接加载快照 | 慢,需逐条重放命令 |
| 数据丢失风险 | 两次快照间的数据全部丢失,最坏窗口取决于 save 规则 | everysec 最多丢 1 秒,always 不丢 |
| 文件体积 | 紧凑,相对小 | 通常更大,靠重写控制膨胀 |
| 对主进程性能影响 | fork 时短暂阻塞,COW 期间内存占用上升 | always 模式写操作性能严重下降 |
| 是否可读 | 不可读,二进制 | 可读,能分析命令流 |
| 是否有修复工具 | 无统一工具,损坏后基本无法恢复 | 有 redis-check-aof 可修复 |
| 复制(主从)场景 | 通常作为主从复制的初始同步文件 | 全量同步后增量数据靠 replbacklog 与 AOF 配合 |
4.1 性能与可靠性的取舍逻辑
RDB 的性能优势在于“批量写”,它通过子进程把内存整体落盘,主进程几乎不参与 IO。而 AOF 是“实时写”,每一条命令都要经过写文件这个环节,无论你怎么优化,单次写入的延迟必然高于纯内存操作。在 p99 延迟敏感的业务里,always模式的 AOF 会明显拉高写入延迟,这是有过实际教训的。
可靠性的逻辑正好相反。RDB 的快照周期决定了数据丢失窗口,哪怕你把 save 调成save 1 1(每秒有一次写就立刻保存),fork 的成本也会让你得不偿失。AOF 的everysec和always则提供了更细粒度的数据保护,把丢失窗口精准控制到一个命令或一秒。
4.2 恢复时间的真实差距
我用一个 8GB 数据集、普通 SSD 磁盘的环境实测过:RDB 文件加载大约 30 到 50 秒完成;AOF 用默认everysec外加每 128MB 自动重写,文件大约 5GB,恢复时间基本要 3 到 5 分钟。这中间的差距对高可用场景影响极大——Redis 故障恢复期间,整个缓存层都处于空窗期,数据库压力直接翻倍甚至被打崩。
这也是为什么很多 Redis 服务端架构在做故障恢复时,会优先选择“先加载 RDB 快速提供基本服务,后台再异步重建并切到 AOF”这种复杂策略。当然,这是中间件层面才需要解决的问题,单机场景下你意识到恢复速度的差距就够了。
4.3 混合持久化:Redis 4.0 的集大成方案
很多人不知道,Redis 4.0 开始支持了混合持久化(RDB + AOF)。这个方案完美结合了两者的优点。开启方式:
aof-use-rdb-preamble yes开启后,AOF 文件的结构变成:以 RDB 格式开头,后续再追加 AOF 命令。也就是说,Redis 启动加载时先读取 AOF 文件中的 RDB 部分(数据量大的这部分恢复速度接近 RDB),再重放后面的增量命令(数据量小、速度快)。这样既解决了恢复慢的问题,又保证了数据不丢失。这是目前生产环境最推荐的持久化组合。
要注意的是,混合持久化并不是独立于 AOF 的第三种机制,它本质上是 AOF 文件格式的改良。所以使用它,仍需要appendonly yes。
5. 生产环境选型与最佳实践
讲清楚原理之后,落到实际选型,我把自己做过的项目里踩过的坑和总结出的策略一次说清楚。
5.1 不同业务场景下的配置建议
场景一:缓存型业务,允许少量丢失
典型的场景是 Session 缓存、热点数据缓存。就算 Redis 重启丢了最近几分钟的数据,数据库里还有原始数据,可以从 DB 重新加载到缓存。这种场景可以只开启 RDB,配置save 900 1或更宽松的规则,甚至完全关掉持久化都行(纯内存缓存)。
场景二:数据库型业务,数据不能丢
比如把 Redis 当库存、余额、订单状态这类关键数据存储来用。这种场景必须开 AOF,并且建议appendfsync everysec。如果你想做到一条不丢,用always,但要评估写入性能是否扛得住。同时保留 RDB,利用混合持久化模式,恢复时才不遭罪。
场景三:大数据量、高可用敏感场景
推荐开启混合持久化,配置appendonly yes + aof-use-rdb-preamble yes,再设置合理的 AOF 重写触发阈值。这样做到数据不丢、恢复快、文件不至于无限膨胀。
5.2 我认为很关键的三条配置细节
第一,RDB 的save规则不要设置得太激进。很多人为了“更安全”,把 save 写成save 60 10,结果发现磁盘 IO 变得很高,fork 也非常频繁,反而拖垮了主进程。每多一次 fork,阻塞时间可能增加几十毫秒,在高 QPS 业务下这是不可接受的。
第二,AOF 重写期间记得监控磁盘空间。AOF 重写会先生成一个新文件,再替换旧的,所以磁盘上短时间会存在两份 AOF 文件的体积之和。如果磁盘空间只有 30GB,AOF 已经有 28GB,重写直接失败,甚至可能因为没有临时空间导致写操作失败。这个坑一定要提前留足磁盘余量。
第三,备份文件不要只放在本机。无论 RDB 还是 AOF,本机磁盘挂了你什么都找不回来。正经做法是配合定时任务,把 RDB 文件压缩后传到对象存储或者异机备份目录。恢复时从备份拉回文件,再手动放到 Redis 的dir目录下。
5.3 关闭持久化的正确姿势
有些性能敏感型业务会故意关闭持久化,让 Redis 变成一个纯内存缓存。配置方法是把save全部注释掉,并且appendonly no。但我遇到过有人只注释了 save,却忘了 AOF 还开着,结果日志越攒越多,磁盘直接打满。关闭持久化时,务必确认以下全局配置都到位:
save ""或者全部注释 save 规则appendonly no- 重启 Redis 前确认没有残留的
dump.rdb或appendonly.aof文件(否则启动时还是会尝试加载)
另一件要注意的事:即使关闭了持久化,如果你用SAVE手动执行一次,Redis 仍然会把数据写入 RDB 文件。所以运维脚本里如果用了SAVE,实际上还是会产生磁盘文件。
6. 常见问题与排查技巧实录
这块我把实际遇到过的问题整理成一个速查表,一些偏门的“坑”也一并列出来。
6.1 速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| AOF 文件损坏导致 Redis 无法启动 | 非正常停机、磁盘写入中断 | 运行redis-check-aof --fix修复 |
| RDB 文件损坏,服务起不来 | 磁盘写满、IO 异常导致快照写入不完整 | 从备份恢复,RDB 无自动修复 |
| 持久化目录磁盘满,写操作报错 | dir指向的分区空间不足 | 清理日志、扩容、或者更改持久化目录 |
| fork 操作导致主进程卡顿 | 实例内存过大,fork 复制页表耗时长 | 错峰执行 BGSAVE,或考虑集群切片 |
| BGSAVE 后内存瞬间上涨很多 | COW 机制复制了大量内存页 | 错峰操作;适当缩小单实例内存 |
| AOF 文件疯狂增长 | 重写失败 / 重写条件未触发 | 手动BGREWRITEAOF,检查磁盘余量 |
| 启动时加载了旧数据,新数据丢失 | 持久化触发频率不够 | 调整 save 规则;开启 AOF 并提高 fsync 频率 |
6.2 “丢了 30 分钟数据”的经典事故分析
有一次我们生产环境的 Redis 挂了,恢复后客户反馈缓存里的数据普遍是旧了半小时的。查下来原因很典型:业务方只开了 RDB,配置还用的是默认save 900 1,也就是最坏丢 15 分钟——但因为那段时间写入量还没达到阈值,加上服务器突然断电,实际丢了 30 多分钟。这就是 RDB 的天然短板叠加配置不当的双重结果。
后来我们把持久化策略改成了appendonly yes + appendfsync everysec + aof-use-rdb-preamble yes,再配合 RDB 定时备份。从那以后,类似的事故再没有出现过。这件事给我最大的教训是:当你不确定业务能容忍丢多少数据时,用 AOF 兜底永远是更稳的选择。
6.3 关于 RDB 文件加载失败的一个偏门问题
有一种情况你可能想不到:RDB 文件本身没问题,但 Redis 启动时加载失败,错误日志里提示“short read or OOM loading DB”。这通常不是文件损坏,而是机器内存不足以容纳整个数据集。RDB 加载是直接把数据集全部载入内存的,如果物理内存不够,加载过程就会失败。解决办法很直接:给机器加内存、或者启用 Redis 的虚拟内存机制(不推荐)、或者考虑混合持久化和集群拆分。
7. 写在最后,也是我最想告诉你的一件事
如果你只能记住这篇文章里的一个结论,我希望是:永远不要把持久化当成“开没开”的问题来看,而要当成“数据能丢多少、恢复要多快”的权衡题来设计。RDB 和 AOF 没有绝对的谁好谁坏——RDB 恢复快、体积小,但数据丢失窗口大;AOF 数据安全、可读可修,但恢复慢、文件大。最理想的方案就是让它们在混合模式下各司其职,RDB 负责快速重建数据底子,AOF 负责补齐增量、把丢失窗口压到秒级。
我建议各位可以试着在自己的测试环境里做一次“破坏性实验”:开两个 Redis 实例,一个只配 RDB,一个配 AOF + everysec,分别写入同样多的数据,然后直接kill -9杀掉进程,再重启看看数据差异。这种亲手操作比看一百篇对比文章都管用。等你真正在日志里看到那几行“Missing N changes in the last M seconds”的警告,你就彻底明白为什么要重视持久化了。