直接以博文开始:
1. 先别急着背面试题,Redis持久化到底在解决什么问题
我刚入行那几年,一直把Redis当缓存用,觉得“挂了重启一下,数据丢了就丢了,反正是缓存”。直到有一次线上活动,运营在Redis里存了一批预热的热门商品数据,结果半夜Redis实例内存异常退出,重启之后数据全没了,活动页直接变成空白。从那以后我才真正意识到,Redis持久化机制不是“后端面试八股文”,而是每一个用Redis的人必须想清楚的事。
说白了,Redis是一个基于内存的键值数据库,读写都在内存里完成,速度快得离谱,但内存是易失的,进程一挂、机器一重启,数据就没了。持久化机制就是在内存数据之外,通过RDB快照或AOF日志把数据落盘,让Redis即使崩溃重启,也能把数据恢复回来。这篇文章要说的,就是RDB和AOF这两兄弟到底有什么区别,各自的原理、配置、适用场景是什么,以及在实战中你会踩到哪些坑。
这篇内容适合谁?适合刚接触Redis的初学者,也适合在生产环境维护Redis的开发、运维和架构师。我会尽量讲清楚底层机制,也会给出一套可直接落地的配置和运维建议,保证你看完不只是“记住了区别”,而是真的能在自己的项目里做对选择。
2. RDB和AOF的核心机制到底差在哪
2.1 RDB:内存快照的瞬间凝固
RDB(Redis DataBase)持久化的方式非常简单粗暴:在满足触发条件时,Redis会fork一个子进程,由子进程把当前内存中的全部数据生成一份完整的二进制快照文件,默认文件名是dump.rdb。这个文件是经过压缩的二进制格式,恢复数据时就通过加载这个文件来重建整份内存数据。
关键点是fork。Redis主进程fork出子进程后,子进程拥有主进程内存页的副本(借助操作系统的写时复制机制),然后子进程负责把内存数据写入临时文件,写完后原子性地替换旧的rdb文件。这样做的好处是主进程不需要暂停服务,可以继续处理客户端请求。但有个前提条件:如果fork期间发生大量写入,写时复制会让主进程的内存开销翻倍,这就是为什么有些大实例在RDB快照时内存会暴涨。
RDB的触发方式有两种:手动触发和自动触发。手动用SAVE命令会阻塞主进程,生产环境基本不用;用BGSAVE命令则是异步生成快照,这是常规操作。自动触发则是通过配置文件里的save指令,比如配置save 900 1,意思是900秒内至少有1次写操作就触发一次BGSAVE。我常用的一套配置是save 900 1、save 300 10、save 60 10000,分别对应不同频率的写入量。
RDB最大的优点就是文件压缩率高、恢复速度快,非常适合做全量备份和灾难恢复。但它也有一个天然缺陷:快照之间发生的数据变更会丢失。比如你配置了每5分钟生成一次快照,那最近这5分钟内写入的数据在宕机后就会丢掉。这不一定是坏事,关键是你要清楚自己能容忍丢多少数据。
2.2 AOF:命令日志的逐条回放
AOF(Append Only File)的思路和RDB完全不同。它不生成快照,而是把每一条写操作命令(SET、RPUSH、DEL等)以Redis协议格式追加到一个日志文件里,默认文件名是appendonly.aof。当Redis重启恢复时,就是把AOF文件里的所有命令重新执行一遍,从而把数据“重演”出来。
因为AOF记录的是命令,所以数据安全性取决于刷盘策略。Redis提供了三种appendfsync配置:
always:每执行一条写命令,立即调用fsync把命令写入磁盘。这种策略数据最安全,最多丢失一条命令,但磁盘IO开销巨大,吞吐量会被拖垮。everysec:每秒执行一次fsync,把缓冲区的命令刷到磁盘。这是默认值,也是我推荐的折中方案,宕机时最多丢失一秒的写入数据。no:不主动调用fsync,完全交给操作系统去决定何时刷盘。这种策略性能最好,但崩溃时可能丢失较多数据,而且丢多少不可控。
AOF文件是追加式的,会随着运行时间不断膨胀。为了解决这个问题,Redis引入了AOF重写机制。重写不是压缩原有的日志文件,而是基于当前内存中的数据生成一套最小的、可以恢复当前状态的需求命令集,再覆盖掉原来的AOF文件。比如你对同一个key写了100次,重写时只会保留最后一次赋值的那条命令。
AOF重写也有BGREWRITEAOF手动触发和自动触发两种方式。自动配置里有两个关键参数:auto-aof-rewrite-percentage和auto-aof-rewrite-min-size。默认当AOF文件大小超过上次重写后文件大小的100%,且文件超过64MB时,触发自动重写。重写过程同样会fork子进程,主进程一边把新命令写入旧AOF文件,一边把增量命令存入重写缓冲区,子进程重写完成后,再把这些增量命令追加到新文件里,最后原子替换。
2.3 两种机制的直观对比
很多人纠结RDB和AOF哪个好,其实没有绝对的答案,它们是完全不同维度的设计。我列一张对比表,把核心差异说清楚:
| 维度 | RDB | AOF |
|---|---|---|
| 数据格式 | 二进制压缩快照 | 文本协议命令日志 |
| 数据安全性 | 可能丢失两次快照间的数据 | 取决于fsync策略,everysec最多丢1秒 |
| 恢复速度 | 快,直接加载二进制文件 | 慢,需要逐条重放命令 |
| 文件体积 | 通常更小,且是压缩的 | 通常更大,需要定期重写 |
| 对性能影响 | fork瞬间有CPU和内存开销,之后无IO开销 | 持续追加写文件,影响磁盘IO和吞吐量 |
| 适用场景 | 备份、快速恢复、容忍部分数据丢失 | 对数据安全要求高,不能丢太多数据 |
| 启动优先级 | 低 | 高,如果同时开启,优先加载AOF |
这里要注意一个重要细节:Redis 4.0以后支持混合持久化。开启aof-use-rdb-preamble yes后,AOF文件头部会先用RDB格式保存当前内存快照,RDB部分后面再追加增量命令日志。这样既保留了RDB恢复快的优点,又结合了AOF的强安全性。后面我会专门讲混合持久化怎么配。
2.4 为什么RDB和AOF不能简单二选一
我见识过不少团队,图省事只开RDB,结果数据丢得惨不忍睹;也有团队开AOF用always策略,结果机器CPU被打满,还不如不开。核心原因是:你选择的持久化方式必须和你的业务数据形态匹配。
举个例子,如果你的Redis里存的是用户会话、防重令牌这种短期数据,丢了也无所谓,那RDB完全够用,甚至可以不开启持久化。但如果Redis里存了订单状态、支付流水、任务队列等关键数据,那至少要开启AOF,而且要把appendfsync设为everysec,否则Redis一崩你就要肉疼了。
还有一个容易被忽略的点:RDB和AOF不是互斥的,可以同时开启。Redis默认会优先用AOF文件来恢复数据,因为AOF的数据完整性更好。同时开启的好处是,既能用RDB做快速备份和迁移,又能用AOF保障近期数据不丢。坏处是写入放大和磁盘占用会多一些,但这在生产环境里通常可以接受。
3. 配置与实战:从默认参数到混合方案
3.1 RDB配置逐项拆解
RDB相关的配置项不算多,但每个都有讲究。我拿一份典型的redis.conf片段来逐行说明:
save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dbfilename dump.rdb dir /var/lib/redis先说save。三条规则构成一个“或”的逻辑,只要满足任意一条就触发BGSAVE。第一行是15分钟内至少1次写入,第二行是5分钟内至少10次写入,第三行是1分钟内至少10000次写入。这是一种分层设计:写入越频繁,快照间隔就越短,尽可能把丢失窗口压缩小。如果你的写入量极大,比如每秒几万次,那60秒10000次很容易被触发,快照频率就会很高,要注意磁盘IO压力。
stop-writes-on-bgsave-error我建议保持默认的yes。这个参数的意思是,如果BGSAVE失败(比如磁盘满了或权限异常),Redis会拒绝后续的写请求。很多人觉得这样不友好,毕竟缓存服务怎么能不让写?但实际上,如果你关掉这个选项,BGSAVE连续失败你也不知道,数据持续在裸奔,等真出问题时就晚了。快照失败时宁可让服务短暂不可写,也不能闷声丢数据。
rdbcompression yes表示快照文件用LZF算法压缩,能大幅减小rdb文件体积,代价是备份和恢复时会消耗一点CPU。默认开启就好,除非你内存冗余且CPU紧张,否则没必要关闭。
rdbchecksum是RDB文件的校验和,加载时如果发现文件损坏会拒绝启动。这个必须开着,否则一个坏文件可能导致Redis直接起不来或悄悄丢数据。
dbfilename和dir是文件路径配置。这里我要特别提醒:dir是相对路径,配置成绝对路径最稳妥。另外备份文件最好放在独立磁盘或挂载点,避免和数据盘一起写爆。
3.2 AOF配置逐项拆解
AOF的配置看起来更简单,但坑也更多。核心配置如下:
appendonly yes appendfilename "appendonly.aof" appendfsync everysec no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb aof-load-truncated yes aof-use-rdb-preamble yesappendonly yes就是开启AOF。第一次开启时会生成一个空的aof文件,Redis重启时会去加载它。
appendfsync我前面已经说过,生产环境默认everysec即可。这里想多说一句:always看似最安全,但在高并发场景下,每条命令都fsync一次,磁盘IO会成为瓶颈,吞吐量可能下降50%以上。除非你数据重要性高到“丢一条就是事故”,否则不要用always。
no-appendfsync-on-rewrite默认是no,意味着在AOF重写期间,fsync策略依然生效。如果设为yes,重写期间不执行fsync,性能更高但可能丢失更多数据。我建议保持默认,因为重写本来就会消耗资源,再放宽刷盘策略不明智。
auto-aof-rewrite-percentage 100和auto-aof-rewrite-min-size 64mb是重写阈值。aof文件比上次重写后的大小增长100%,且超过64MB时触发重写。比如上次重写后是100MB,AOF涨到200MB就会触发。实际生产里,如果你的写入量稳定,重写会很规律;如果写入量暴增,AOF文件可能会在短时间内翻几倍,重写也会更频繁。
aof-load-truncated yes解决的是AOF文件尾部截断的问题。比如Redis通过AOF恢复时发现文件最后一条命令不完整,默认会忽略这条损坏的命令并正常启动,同时把截断情况记录在日志里。这是“救命”配置,千万别改成no,否则一个小问题就会让Redis拒绝启动。
aof-use-rdb-preamble yes是混合持久化的开关,Redis 4.0之后默认开启。开启后AOF文件前面是RDB二进制格式,后面是增量命令,兼顾加载速度和数据安全。下面我会单独讲它。
3.3 混合持久化:AOF头部塞进一份RDB快照
混合持久化可能是很多同学忽略的一个特性。在没有它之前,Redis重启时如果加载AOF,就要把整个AOF文件里的命令一条条重放。如果AOF文件是10GB,哪怕数据最终只有几百MB,加载过程也可能要几分钟。而RDB文件因为已经是内存数据的二进制形态,加载速度快得多。
混合持久化就是把这个思路做到极致:AOF重写的时候,先用RDB格式把当前内存快照写入AOF文件头部,然后把重写期间新增的写命令追加到RDB部分之后。这样最终的AOF文件是一个“RDB打底 + AOF增量”的混合体。Redis重启时,先加载RDB部分恢复大部分数据,再重放后面的增量命令补齐最后几秒的数据。
我实测过一个场景:一个包含500万key、约4GB数据的Redis实例,纯AOF文件大约3.2GB,冷启动加载耗时接近40秒;开启混合持久化后,同一份数据AOF文件头部是2.8GB的RDB快照,加载耗时缩短到约8秒,而且只丢了最多1秒的增量数据。这就是为什么我强烈建议,只要是Redis 4.0以上版本,aof-use-rdb-preamble yes一定要开着。
3.4 手动备份与快速恢复实操
除了依赖自动持久化,我每周还会做一次手动全量备份。最简单可靠的方式是执行BGSAVE生成RDB快照,然后把这个dump.rdb文件复制到备份服务器或对象存储里。注意,直接复制运行中的dump.rdb是不安全的,必须等BGSAVE完成后再复制,否则可能拿到一个正在写入的半成品文件。
手动恢复也很直接:假设你把备份的dump.rdb拿回来,放到Redis配置的dir目录下,文件名和配置里的dbfilename一致,然后启动Redis。启动时Redis会优先检测AOF文件是否存在,如果存在就加载AOF,否则加载RDB。所以如果你想强制恢复RDB,同时AOF也开着,最保险的做法是先临时关闭AOF(appendonly no),启动完成后再开启AOF并执行一次BGREWRITEAOF重建AOF文件。
这里我插一个经验:不要裸奔着拷贝AOF文件做备份。AOF是追加写入的文件,边写边拷很容易出现文件不一致。正确做法是先用BGREWRITEAOF生成一个完整重写后的AOF文件,再复制它。或者干脆用RDB做外部备份,AOF只做内部恢复,这个组合在绝大多数场景下都够用了。
3.5 数据一致性验证
恢复完数据后,别急着宣布“搞定了”。我踩过一种情况:Redis恢复成功但数据实际是旧版本,原因是RDB文件加载失败时Redis会尝试加载AOF,但AOF文件开头被写坏了。为了验证恢复结果,我会用redis-cli --bigkeys看key数量分布,或者用DBSIZE看key总数,再和自己备份前的记录比对一下。
更严谨一点的做法是:在备份前记录关键业务key的值,恢复后逐一校验。比如备份前用GET读一个订单状态,恢复后再次读取,确认一致。如果用的是AOF恢复,还可以在日志里看看加载了多少条命令,再和预期对上。
4. 什么场景选RDB,什么场景选AOF
4.1 缓存场景选型:能容忍丢数据的选RDB
如果你的Redis定位就是缓存,比如热点数据、榜单、分类树这类可以从数据库或上游接口重新构建的数据,那我对你的建议是:直接关闭持久化,或者只开RDB。因为缓存数据的本质是“可丢失的加速层”,你为它付出的每一点持久化成本,都是在为不可控的宕机买单。
我见过一个电商项目,把商品详情页的Redis缓存开了AOF everysec,结果每次大促时磁盘IO被打满,缓存写入阻塞,反而把服务拖垮了。后来我帮他们把Redis降级成不持久化,同时利用RDB每5分钟做一次兜底快照,大促时问题直接消失。原因是缓存数据本来就可以从数据库回源,持久化损失的那点数据无关痛痒,但省下的磁盘IO和CPU都是实打实的。
4.2 强一致需求场景:必须上AOF并谨慎处理刷盘策略
如果Redis里存的是有状态数据,比如分布式锁信息、扣减库存的计数器、任务队列的待消费消息,那丢失数据是不可接受的。这种情况下至少开启AOF,并且appendfsync用everysec,极端情况下最多丢一秒的数据。
有些同学会想:“我用always策略,是不是就绝对不会丢数据?”理论上是这样,每条命令都落盘才算成功,但这也意味着Redis的写性能会被拖到和机械硬盘一个水平,很多业务根本承受不了。我自己在金融类项目里也不会开always,而是用everysec + 主机高可用 + 定期RDB备份的组合。分布式锁这类场景更是要配合Redisson或红锁机制,在Redis外部兜一层底,不能把宝全押在持久化上。
4.3 性能敏感场景选型:用RDB做快照,用AOF做底线
性能敏感但又不希望丢太多数据的场景是最常见的。比如登录会话、秒杀活动、消息列表这类数据,用户容忍短暂异常,但完全丢失也很伤。我建议同时开启RDB和AOF,用RDB完成定期全量备份和快速恢复,用AOF everysec补充秒级数据,再把混合持久化打开,让恢复速度接近纯RDB。
开启混合持久化之前,最好先测算一下磁盘带宽。AOF本身是追加写,混合持久化在重写时还会额外生成RDB格式的快照,瞬时IO压力会比较大。我在一个每秒写入1.2万次的实例上做过压测,混合持久化开启后,磁盘写吞吐比纯AOF高了约15%,但在SSD上完全可以接受。如果你的实例用机械盘,建议给Redis配专门的盘,或者调大重写阈值以减少重写频率。
4.4 一家之言:多数情况下我推荐“默认+混合”
如果你问我在新项目里怎么配,我给出的答案是:appendonly yes+appendfsync everysec+aof-use-rdb-preamble yes,加上RDB的默认快照规则。这套组合在数据安全、恢复速度、性能开销之间取得了最好的平衡,也是我目前给团队定的标准模板。
当然,“默认+混合”不是万能的。如果内存超过20GB,fork带来的阻塞风险就要重视,这时候要调大save的阈值,比如减少自动快照频率,甚至只在凌晨低峰期手动执行一次BGSAVE。做技术选型从来不是选“最好的”,而是选“副作用最少、最匹配你现状”的。
5. 日常运维中那些坑与排查实录
5.1 fork阻塞与大key注意事项
RDB和AOF重写都依赖fork,而fork的耗时和Redis进程占用的内存成正相关。我见过一台内存48GB、实际使用38GB的Redis实例,执行BGSAVE时fork瞬间阻塞了主进程将近3秒,所有读写全部卡死。那次事故给我的教训是:Redis节点内存不是越大越好,比如超过20GB时就要考虑分片或者集群,把单个实例的内存压下去。
更隐蔽的是大key。一个包含几百万元素的Hash类型,在fork重写时会导致写时复制的内存页数量剧增,极端情况下内存飙升到实例上限的2倍,直接触发OOM。所以我每季度都会用redis-cli --bigkeys排查一次大key,遇到超过100MB的高频访问key,优先拆成多个小key或改走其他存储。
5.2 AOF重写频繁触发的排查
AOF重写太频繁会拖垮性能。我遇到过一种情况:业务系统每秒钟写入海量key,AOF文件每5分钟就从64MB涨到130MB,自动重写几乎变成持续行为,磁盘IO和CPU占用双双飙高。排查后发现,原来是某个定时任务在循环创建临时key,而且没有设置过期时间,数据只增不减,AOF自然疯狂膨胀。
解决方案分两步:第一步,给临时key加上合理的TTL;第二步,把auto-aof-rewrite-min-size从64MB调到256MB或512MB,降低重写触发频率。调大min-size要谨慎,重写间隔拉长意味着AOF文件可能在较长时间里保持较大体积,磁盘占用和恢复时间都会变长。你需要在重写频率和文件体积之间找一个平衡点。
5.3 估算宕机时最多会丢多少数据
这个问题面试官喜欢问,但真正的运维人员也应该心里有数。假设你的配置是save 900 1加appendfsync everysec,RDB的丢失窗口长达15分钟,但AOF只会让你丢最多1秒。所以同时开启RDB和AOF时,实际的丢失窗口取决于AOF里刷盘延迟,而不是RDB。
如果你只开RDB、配置save 60 10000,在写入量恰好低于10000次/分钟的边缘情况下,最坏可能丢掉15分钟内的全部数据。我专门写过一个脚本模拟并发写入后kill -9 Redis,观察for循环里丢了多少条数据。实测结果是:save 60 10000+ 无AOF,瞬间kill后丢失约三分之二的数据,惨不忍睹。这就是为什么有写入量的生产环境绝不能只依赖RDB。
5.4 混合持久化优先级验证:RDB和AOF同时存在时谁说了算
有一个细节很多教程没讲透:Redis在启动时发现RDB和AOF都存在,到底加载哪个?答案是AOF优先。我专门验证过:同一个Redis目录下,dump.rdb里是旧数据,appendonly.aof里是新数据,启动后数据和新AOF一致。这是因为AOF文件的数据完整性更好,Redis默认认为AOF比RDB更新,原因在于开启AOF后每次写命令都会追加,而RDB可能停留在上次快照的时刻。
这带来一个实用的运维习惯:如果你想用RDB备份文件恢复线上数据,一定要把Redis停掉、把AOF文件移走,再启动,否则你会一脸懵地看着数据“没恢复成功”。我早期就在这个细节上栽过跟头,备份恢复演练时数据一直不对,排查了一下午才发现是AOF在捣乱。
5.5 定时备份与保留策略建议
最后分享一套我用了很久的备份方案。每天凌晨4点低峰期执行一次BGSAVE,然后通过cron脚本把dump.rdb拷贝到备份目录,保留最近7天的RDB文件;每周日再额外保留一份“周备份”存档。同时AOF保持everysec刷盘,不单独备份AOF文件,因为它只是补充秒级数据,真正需要追查历史数据还得靠RDB。
备份这件事最怕“备了但没法用”。我建议每季度做一次恢复演练:拿一个临时的Redis实例,把最新备份的RDB文件放进去启动,跑一遍关键查询,确认数据毫发无损再放心。没做过恢复演练的备份,约等于没有备份,因为没人知道它在真实故障下能不能站得住。
我个人在实际操作中还有一个体会:不要轻易把持久化关掉,即使是短时间关掉也要写进变更单。我曾经因为要压测性能而临时关掉了AOF,压完忘开,结果当晚服务宕机丢了一个多小时的数据。这类低级事故真的不应该发生,建议各位在每次改完Redis配置后,都顺手用CONFIG REWRITE把内存配置同步回配置文件,再复查一遍CONFIG GET appendonly,确认开关状态符合预期。持久化这件事,平时看不出差别,真到了重启那一刻,你才会明白当初按下的每一个开关都有代价。