1. 持久化前夜:先搞清楚Redis为什么会丢数据
很多人第一次接触Redis持久化,都是因为线上出了事故。某天突然发现缓存里的数据全没了,重启之后Redis干干净净,就像失忆了一样——然后才想起自己从来没配置过持久化。这不是个例,我见过太多开发者在本地玩Redis玩得很开心,到生产环境直接用默认配置,结果一次重启或宕机就丢光了所有数据。
要理解RDB和AOF的区别,得先搞清楚一个基础问题:Redis为什么默认不持久化,或者说,为什么它会有丢失数据的风险?
Redis是一个内存数据库,所有读写操作都发生在内存里,这也是它每秒能扛住十万级QPS的原因。内存快,但内存有个致命缺点——断电即失。Redis进程一退出,操作系统把内存一回收,里头的所有数据就没了。
持久化说白了就是一件事情:把内存里的数据想办法落到磁盘上,让它在进程重启之后还能找回来。RDB和AOF就是Redis提供的两种落盘思路,它们的出发点和侧重点完全不同,后面会详细拆。
先说结论级别的差异:RDB是“定期拍照存档”,AOF是“实时记操作日志”。一个偏重性能恢复快,一个偏重数据安全丢得少。具体怎么选,取决于你的业务场景——但绝大多数场景下,正确答案是“两个都要”。
2. RDB的原理与实践:拍照存档的思路
2.1 RDB到底是怎么工作的
RDB全称是Redis Database Backup File,它做的事情很直白:在某个时间点,把Redis内存中的数据完整生成一份快照,写入磁盘上一个后缀为.rdb的文件里。
你可以把它理解成给手机拍合影。拍摄的一瞬间,所有人的表情、姿势、位置都被定格在照片上。RDB也是这个逻辑——触发保存时,Redis会把当前内存里每一个key、每一个value、每一条过期信息,连同数据库编号等元数据一起,序列化写入rdb文件。
这份rdb文件是二进制格式的,紧凑、压缩、体积小,非常适合做备份和迁移。比如凌晨三点系统低峰期,定时触发一次RDB生成,把这个文件拷贝到别的机器上,就能实现数据迁移或冷备。
2.2 触发方式:手动、自动与被动
RDB的生成有三种触发路径,实际操作中主要用前两种:
手动触发
- save命令:同步生成RDB快照。所谓同步,就是在save执行的整个过程中,Redis的主进程会被阻塞,停止响应所有客户端请求。数据量大时,这个阻塞可能持续数秒甚至更久。生产环境几乎不会主动用save。
- bgsave命令:异步生成RDB快照。Redis主进程会fork出一个子进程,由子进程负责把内存数据写入rdb文件,主进程继续服务客户端的读写请求。这是生产环境的标准做法。
fork这个词对新手可能有点陌生,简单解释一下:fork是操作系统提供的一种进程复制机制,Redis主进程调用fork后,会得到一个和自己几乎一模一样的子进程。子进程继承主进程当时的全部内存数据,然后子进程负责把这些数据写入磁盘。由于操作系统有写时复制(Copy On Write)技术的加持,fork出来的子进程在写磁盘期间,主进程的写入操作并不会被阻塞,而是通过复制内存页的方式保证数据一致性。
自动触发
- 通过配置文件里的save指令,满足条件时Redis会自动执行bgsave。配置格式是
save <seconds> <changes>,意思是“如果seconds秒内发生了changes次以上修改,就触发一次RDB保存”。 - 默认配置通常是这三条:
save 900 1:900秒(15分钟)内至少1次修改,触发保存save 300 10:300秒(5分钟)内至少10次修改,触发保存save 60 10000:60秒内至少10000次修改,触发保存
- 注意,这三个条件不是“同时满足”,而是“任意满足一条就触发”。它是或的关系,三条都命中也没关系,Redis会合并处理。
被动触发
- Redis在关闭进程(正常shutdown)时,如果开启了RDB,会执行一次save,把内存数据完整落盘后再退出。
- 在主从复制场景中,从节点全量同步时,主节点会自动生成一份RDB发送给从节点。这个属于复制机制内部行为,顺便也会生成rdb文件。
2.3 RDB的文件长什么样
rdb文件是一个二进制文件,你没法直接用cat查看内容,但可以通过redis-check-rdb工具检查它的完整性。
如果你好奇文件内部结构,文件里有几个关键部分:
- 开头的REDIS常量标识,加上版本号,类似一条文件头
- 数据库元信息,包括数据库编号、键值对数量等
- 具体的数据内容,每个key、value都以特定编码存储
- 文件末尾的CRC64校验和,用于验证文件是否完整
RDB文件最大的优点是紧凑。我用一个实际项目验证过:一个存有200万个key、内容以字符串和哈希为主的Redis实例,内存占用大约1.8GB,生成的rdb文件只有920MB左右,压缩比接近2:1。这节省的不仅是磁盘空间,恢复时读入内存的数据量也更小,恢复速度自然更快。
2.4 RDB的优缺点:快但会丢数据
RDB的优点是实打实的:
- 文件体积小:二进制+压缩,同样数据量远小于AOF文件。
- 恢复速度快:RDB文件直接恢复到内存,加载过程几乎是顺序读取,几百MB的文件秒级恢复。这点对大型实例很重要。
- 适合备份:rdb文件可以直接拷贝走作为离线备份,迁移、容灾、归档都方便。
缺点也非常明显:
- 数据丢失窗口大:RDB是定时快照,两次快照之间的所有写入,如果实例宕机,就全部丢失。比如你配置了15分钟保存一次,恰好在第14分59秒宕机,那这14分59秒内的所有写入全没了。
- 生成快照开销不小:虽然用的是bgsave和fork子进程,但fork本身在内存大时也很耗时,而且写时复制可能导致内存峰值上涨。数据量大时bgsave瞬间可能会占用额外内存,导致Redis内存使用率升高。
- 频繁生成影响性能:如果保存频率太高,频繁fork子进程,也会对系统资源造成压力。
用一句大白话总结RDB:它能保证你恢复到“某个时间点”的数据,但恢复不到“当前这一刻”。
3. AOF的原理与实践:追加日志的思路
3.1 AOF的核心思想:记录每一次写操作
AOF全称是Append Only File,翻译过来是“只追加文件”。它采用的思路和RDB完全不同——不拍照,而是把每一次修改数据的操作命令,一条一条追加写入到一个日志文件里。
你执行set username zhangsan,AOF文件里就追加一条记录,内容是类似这样的命令文本;你执行hset user:1001 name lisi,AOF文件里同样会追加一条。推而广之,所有写操作都会被记录下来,等Redis重启的时候,把AOF文件里的每一条命令从头到尾执行一遍,数据就恢复了。
这个思路很像记流水账,你每花一笔钱都记在账本上,月底对账的时候翻开账本从头看到尾,就清楚花在哪里了。
AOF文件是文本格式的,可以直接用cat查看内容。这是它的一个隐性优点——可读、可排查,甚至可以手工编辑修复(操作要非常小心)。
3.2 AOF的三种写回策略:刷盘时机的艺术
AOF的关键在于“什么时候把日志真正写到磁盘上”。操作系统对文件的操作是这样的:你调用write写入数据时,数据只是先进了内核缓冲区,并没有真正落盘;要等操作系统自己决定何时刷盘(fsync),或者你显式调用fsync,数据才会真正写入磁盘。这中间的空档,如果机器断电,缓冲区里的数据就没机会落盘了。
Redis提供了三种刷盘策略,在配置文件里通过appendfsync指令控制:
| 配置项 | 刷盘时机 | 安全性 | 性能 |
|---|---|---|---|
| always | 每次写命令后都执行fsync | 最高,最多丢失1次写入 | 最慢,吞吐量下降明显 |
| everysec | 每秒执行一次fsync | 较高,最多丢失1秒数据 | 折中方案,性能影响较小 |
| no | 从不同步执行fsync,交给操作系统决定 | 最低,可能丢失数秒数据 | 最快,由OS刷盘策略决定 |
实测下来,everysec是绝大多数生产环境的默认选择,也是Redis的默认配置。它把数据安全性控制在“最多丢1秒数据”,性能损耗却很小,对QPS的影响大概只有5%到10%。对很多业务来说,丢1秒数据是可以接受的——相比RDB可能丢掉整个快照周期的数据,这个窗口已经小太多了。
always是“把钱花在刀刃上”的选项,适合对数据安全极其敏感的场景,比如订单支付、交易流水等核心数据用Redis做缓存,或者本身就承担了准数据库的职责。但它的代价是每次写操作都要等磁盘刷盘完成,性能损耗非常高,实测在某些机器上能让写入QPS腰斩。
no就有点“听天由命”了,Redis不主动刷盘,交给操作系统自己决定。Linux系统的默认刷盘周期大约是5秒,所以极端情况下可能丢5秒甚至更多数据。这个配置适合那种对性能要求极端、又能容忍数据大量丢失的场景。
3.3 AOF文件的重写机制:日志膨胀的解决方案
AOF有一个让人又爱又恨的问题——文件会越来越大。
你执行100次set price 10和set price 20交替操作,最终有效结果只有一个,但AOF文件里可能记了100条命令。日志越积越多,磁盘占用越来越大,最要命的是:Redis重启时恢复数据,需要把AOF文件里每一条命令都执行一遍,文件越大,恢复越慢。
AOF重写(rewrite)就是来解决这个问题的。它会扫描当前Redis内存里的有效数据,生成一份精简的日志文件,把这个瞬间的数据状态用最少的命令记录下来。比如上面说的100条命令,重写后可能只剩下最后一条set price 20。
重写机制有几个关键细节:
- 重写不阻塞服务:重写过程也是fork子进程来操作,主进程继续接受读写请求。
- 重写期间的新写入会被缓存:子进程重写期间,主进程接收到的新的写命令,会额外存到一个缓冲区里。重写完成后,这些新命令会被追加到新的AOF文件尾部,确保不丢数据。
- 触发方式:手动执行
bgrewriteaof命令立刻重写;自动触发则看配置,比如文件体积达到上一次重写后的两倍且超过一定阈值时,触发重写。具体由auto-aof-rewrite-percentage和auto-aof-rewrite-min-size两项配置控制。 - 重写使文件更加紧凑:经过重写,AOF文件可能从十几个GB缩到几百MB,恢复速度大幅提升。
3.4 AOF的优缺点:安全但恢复慢
AOF的优点集中在数据安全上:
- 数据丢失窗口可控:配合everysec策略,最多丢1秒数据,比RDB安全得多。
- 可读性好:文本日志便于排查,甚至可以人为去掉某些错误命令。
- 恢复策略清晰:把命令重新执行一遍,逻辑直白。
缺点是:
- 文件体积大:相同数据集下,AOF文件通常比RDB大几个数量级。
- 恢复速度慢:尤其是未重写的超大AOF文件,恢复过程可能要执行几十万条命令,耗时以分钟计。
- 刷盘有性能开销:即使everysec策略,持续写入时仍有一定损耗。
AOF像记流水账:好处是一笔不漏,坏处是本子越写越厚,対账时间越来越长。
4. RDB和AOF的核心差异对比
4.1 一份对比表看懂本质区别
把RDB和AOF放在一张表里对比,差异就非常清晰了:
| 对比维度 | RDB | AOF |
|---|---|---|
| 记录内容 | 某一时刻的完整内存快照 | 每一次写操作命令 |
| 文件格式 | 二进制压缩文件 | 文本日志文件 |
| 文件体积 | 小 | 大,需定期重写 |
| 恢复速度 | 快 | 慢,命令逐条执行 |
| 数据安全性 | 丢一个快照周期的数据 | 丢1秒数据(everysec) |
| 对性能影响 | fork时短暂影响 | 刷盘策略影响写入性能 |
| 适用场景 | 备份、迁移、容灾、快速恢复 | 数据安全要求高的场景 |
| 默认配置 | 开启 | 开启(Redis 4.0之后默认开启mixed模式) |
4.2 一句话概括各自的使用场景
选RDB为主的场景:
- 数据冷备、全量迁移、跨机房复制
- 对恢复速度要求极高,能接受分钟级数据丢失的缓存场景
- 比如商品列表、用户会话、一些可以被重新计算的派生数据
选AOF为主的场景:
- 数据敏感,丢秒级数据都无法接受的业务
- 订单状态、账户余额操作记录、支付流水等强一致性要求的数据
- 需要日志排查、审计追踪数据变更的场景
但绝大多数情况,正确答案是两者同时开启。
5. 混合持久化与实战配置
5.1 混合持久化:取长补短的解决方案
从Redis 4.0开始,官方引入了混合持久化机制。这个方案把RDB和AOF的优点合并到一起,核心思路是:用RDB作为基础快照,用AOF作为增量日志。
具体工作方式是这样的:当触发持久化重写时(比如执行bgrewriteaof),Redis会把当前内存中的数据先以RDB格式写入AOF文件,后续的写操作再以AOF格式追加在后面。重启恢复时,Redis先加载RDB部分的数据(快),然后把AOF部分的增量命令再执行一遍(补齐最近的数据变化)。
这个方案有几个显著好处:
- 恢复速度接近RDB,不用从头执行所有命令
- 数据安全性接近AOF,只丢少量增量数据
- AOF文件体积明显缩小,重写后文件里包含的是压缩快照+少量命令
实测效果:一个曾经要执行3分钟才能恢复的纯AOF实例,开启混合持久化后,恢复时间直接压缩到20秒以内。对于大型Redis实例,这个提升是决定性的。
混合模式的开启方式:aof-use-rdb-preamble yes,默认就是开启的。
5.2 生产环境配置参考
我这里给出一份经过生产验证的Redis持久化配置模板,直接贴在redis.conf里参考使用:
# 开启RDB save 900 1 save 300 10 save 60 10000 rdbcompression yes dbfilename dump.rdb dir /data/redis # 开启AOF appendonly yes appendfilename "appendonly.aof" appendfsync everysec no-appendfsync-on-rewrite no # AOF自动重写 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # 混合持久化 aof-use-rdb-preamble yes说几个配置项背后的逻辑:
appendfsync everysec:折中方案,推荐用于绝大多数业务。no-appendfsync-on-rewrite:重写期间是否停止刷盘。设成yes能避免重写时磁盘IO拥堵,但会短暂增加丢数据的风险。对数据敏感的业务,保持no。auto-aof-rewrite-percentage 100:AOF文件体积比上次重写时增长一倍,触发重写。auto-aof-rewrite-min-size 64mb:文件至少要达到64MB才允许自动重写,避免重写太频繁。
5.3 实操:验证持久化恢复流程
纸上谈兵没用,我建议你在本地环境完整跑一遍恢复流程,哪怕只是学习也能建立直观认知。
环境:一个单机Redis实例,设置一个唯一定位数据是否丢失的key。
# 1. 启动Redis redis-server redis.conf # 2. 写入数据 redis-cli > SET marker:001 "data-before-restart" > SET user:1001 '{"name":"zhangsan","age":30}' > SADD tags:redis "persistence" "rdb" "aof" > EXPIRE user:1001 7200 > exit # 3. 让RDB和AOF都有数据后,执行正常关闭 redis-cli shutdown nosave # 4. 用AOF恢复:直接把AOF文件当作恢复源,重启实例 redis-server redis.conf redis-cli GET marker:001 "data-before-restart"这里我故意用了shutdown nosave,意思是关闭时不生成RDB,只依赖AOF恢复。重启后数据还在,说明AOF确实把每一次写操作都记下来了。
接着测试RDB恢复:
# 1. 在Redis运行中执行save redis-cli > SET rdb-only-key "only-in-rdb" > BGSAVE > exit # 2. 再次写入几条数据,但不触发新的RDB redis-cli > SET aof-only-key "only-in-aof" > exit # 3. 立刻kill进程模拟宕机(不要正常shutdown,会触发自动保存) kill -9 <redis_pid> # 4. 重启Redis,数据恢复情况 redis-server redis.conf redis-cli GET rdb-only-key "only-in-rdb" redis-cli GET aof-only-key (nil)看到了吗?在BGSAVE之后写入的aof-only-key丢了,因为RDB快照只记录了BGSAVE那一瞬间的数据。这个实验直观地诠释了“RDB快照只能恢复到某个时间点”。
如果开的是AOF而不是RDB,同样的kill重启后,aof-only-key还在。
5.4 持久化配置不当的典型后果
很多初学者犯过一个经典错误:为了追求性能把appendfsync设成no,结果Redis所在机器断电重启后,丢了几千条用户操作记录。排查时发现AOF文件里最后几千行数据根本没有落盘,因为它们只存在于内核缓冲区里。
另一个常见错误是RDB的save频率设得太高,比如save 60 100——60秒内只要发生了100次写就保存。小流量业务看起来没什么,但大流量场景下几乎每几十秒就触发一次bgsave,fork子进程频繁创建,内存开销飙升,CPU也备受压力。
配置持久化方案前,务必先回答三个问题:我能容忍丢多少数据?恢复需要多快?磁盘IO是否吃紧?答案决定配置方向,成年人三个都要,但要有主次。
6. 常见问题与排查技巧实录
6.1 恢复时文件损坏怎么办
场景:Redis启动时报错,提示RDB文件CRC校验失败,或者AOF文件格式错误。
处理思路:
- RDB文件损坏:用
redis-check-rdb dump.rdb检查损坏情况,工具会给出具体提示。如果有备份,直接用备份文件替换。没有备份就只能接受数据丢失。 - AOF文件损坏:AOF文件末尾经常因为中途宕机出现残留半截命令。Redis启动时如果检测到文件不完整,在
aof-load-truncated yes配置下(默认开启)会丢弃损坏部分并正常启动。你可以用redis-check-aof --fix appendonly.aof尝试修复文件,它会删除异常数据。
但要注意,修复是最后手段。我见过某位开发者拿redis-check-aof --fix一把梭,结果AOF文件被截断后丢失了最后几分钟的有效数据。修复前务必先备份原文件,修复是破坏性的操作。
6.2 RDBAOF混合模式下恢复优先级
开启混合持久化后,Redis启动时的恢复顺序是什么样的?
答案是:只要开启了AOF,Redis就倾向于使用AOF文件恢复,因为AOF文件里的数据通常更新、更全。即使同时存在rdb文件和aof文件,Redis也会优先加载AOF内容。
有个特殊情况需要了解:Redis 4.0之前的版本,AOF文件是纯命令日志,恢复必须逐条执行。4.0之后如果开启了混合模式,AOF文件前面是RDB格式的二进制快照,后面是命令追加。恢复时Redis自动识别文件格式,先加载RDB快照部分,再执行追加的尾部命令。
6.3 fork阻塞问题排查
有段时间我的Redis实例每天凌晨4点准时卡顿,监控显示卡顿持续1到2秒,用户的反映是“偶尔转圈”。
排查过程:
- 查看Redis日志,发现卡顿时间段正好和RDB自动保存时间吻合。
- 检查
info persistence里的rdb_bgsave_in_progress,确认卡顿时有bgsave在运行。 - 深入看fork耗时,发现
fork_time高达数百毫秒——实例内存已经有8GB,fork一个子进程需要复制大量页表结构,耗时自然高。 - 再查系统配置,发现Linux的
vm.overcommit_memory配置不对,导致fork时内存分配不顺畅。
解决方案组合拳:
- 把
vm.overcommit_memory调整为1,允许Redis在fork时不检查内存总量。 - 调整RDB触发频率,把凌晨4点的自动保存窗口错开业务高峰。
- 给实例提配,增加CPU和内存余量给fork操作。
这里要提一个非常实用的经验:fork耗时会随着Redis内存值的增大而增加,如果实例内存超过10GB,每次fork都可能造成明显的阻塞,务必关注info persistence里的fork耗时指标。
6.4 快速排查速查表
| 问题现象 | 可能原因 | 解决方向 |
|---|---|---|
| 重启后数据丢失 | 未开启持久化或RDB两次快照间隔内的写入丢失 | 检查appendonly配置,确认AOF开启状态 |
| AOF文件涨到几十GB | 未配置自动重写,或者重写条件未触发 | 检查auto-aof-rewrite配置,手动执行bgrewriteaof |
| bgsave期间Redis卡顿 | fork耗时过长,内存大或系统内存压力大 | 检查fork_time,调整overcommit配置 |
| 持久化文件损坏无法启动 | 断电、磁盘损坏、文件被截断 | 用redis-check-rdb/aof检查,恢复备份 |
| 恢复时间巨长 | AOF文件过大、未重写或性能差的磁盘 | 开启混合持久化,执行重写 |
| 写入性能突然下降 | appendfsync设为always,或设置了no-appendfsync-on-rewrite | 权衡数据安全与性能,改everysec |
| 磁盘空间告急 | RDB+AOF同时开启且更新频繁 | 规划磁盘容量,开启混合模式,合理配置重写阈值 |
7. 聊聊我踩过的坑和一些建议
7.1 一次让我彻底理解RDB缺陷的线上事故
有次线上缓存集群突然master节点宕机,哨兵切换到从节点后一切恢复,但用户反馈“购物车里的商品大量消失”。查了一圈,根因是master节点的持久化配置是默认的RDB-only,RDB保存周期15分钟。它宕机前最后一次快照生成于12分钟前,这12分钟里用户加购物车的数据全部丢失。
那次事故之后,我给所有Redis实例强制开启AOF,采用everysec刷盘策略,并且每周定时执行bgrewriteaof控制文件大小。这个改动让数据丢失窗口从“最长15分钟”直接压缩到“最多1秒”,代价是写入QPS下降不到10%。
7.2 关于配置调优的几条实操心得
- RDB和AOF尽量都开。不要相信网上说的“缓存场景不需要持久化”。缓存数据丢了确实可以从数据库重建,但重建过程对数据库的压力非常大,尤其高峰时期瞬间涌来几万次缓存击穿,数据库直接被打挂也不是没可能。
- AOF的刷盘策略优先用everysec。always对性能影响太大,no又太危险。除非业务对写入延迟极其敏感且能接受秒级数据丢失采用no,否则默认everysec就好。
- 定期巡检AOF文件大小。我习惯每周一次登录Redis执行
info persistence检查aof_current_size和aof_rewrite_base_size,一旦发现文件体积增长超出预期,马上排查写入是否异常。 - 备份务必离线。持久化文件在本机只能防“进程重启”,防不了“机器损坏”。我一般会把rdb文件通过定时任务推送到另一台存储机器或对象存储。有了离线备份,心里真的踏实很多。
- 小心裸金属机器的掉电。云环境里的Redis实例相对安全,但自建机房、裸金属部署的Redis,掉电是持久化文件损坏的第一大原因。物理机环境下建议配上UPS和定期完整性检查脚本。
7.3 两条关于恢复的独门经验
第一,恢复前先检查文件完整性。即使Redis能正常启动,也不意味着文件没有隐患。我习惯在恢复之前先手动执行redis-check-rdb dump.rdb或redis-check-aof appendonly.aof做一次体检,确认文件没问题再启动,避免启动后才发现数据缺失。
第二,全量恢复场景,先恢复RDB,再恢复AOF增量。如果要从远程存储拉取备份恢复实例,优先恢复最近一份rdb文件到Redis里,然后把rdb之后同步的AOF文件或增量命令补回去。这个方案恢复速度比纯AOF恢复快得多,数据也不丢。
Redis持久化不是一道选择题,更准确说是一道组合题。RDB保底,AOF兜底,混合持久化让两者优势叠加。搞懂了底层原理,再看线上各种配置就会觉得豁然开朗。多花点时间在持久化上,远比多调几个性能参数值——数据都保不住的时候,谈性能又有什么意义呢。