Redis 持久化:RDB vs AOF 与混合模式
Redis 是内存数据库,但数据不能只存内存——断了就没了。本文讲清两种持久化机制:RDB(快照,周期性全量 dump)、AOF(追加日志,记录每次写),各自的优劣、RDB+AOF 混合模式(生产推荐),以及 fork 延迟/AOF 重写/恢复速度的取舍。
本文要点
- 主题聚焦:DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
Redis 持久化:RDB vs AOF 与混合模式
Redis 把所有数据放在内存里跑,快是真快,但内存这东西——掉电就没。
所以"持久化"这件事,本质上是在回答一个问题:进程挂了、机器宕了、断电了,重启之后我还能拿回多少数据?能多快拿回来?要付出什么代价?
这篇把 Redis 的三种持久化方案——RDB、AOF、RDB+AOF 混合模式——一次讲透,并给出我在生产环境里真实的选型逻辑。
一、为什么 Redis 需要持久化
很多人对 Redis 的第一印象是"内存数据库"。这话只对了一半——Redis 的运行时确实是纯内存的,但它并不是"只活在内存里"。
为什么要持久化?三个很现实的场景:
- 重启不丢数据。无论是发版重启、配置变更、还是 OOM 被 kill 掉,进程一旦退出,内存里的数据就灰飞烟灭。对缓存场景来说大不了再回源重建,但对会话、消息队列、计数器、限流、排行榜这类"准业务数据",丢了就是生产事故。
- 宕机恢复。机器掉电、内核 panic、硬件故障,这种非优雅退出下内存数据全部丢失。如果没有持久化文件,Redis 重启后就是一个空实例。
- 主从全量同步。新加的从节点、断线重连的从节点,需要从主节点的 RDB 快照开始做全量同步。持久化文件是主从复制的载体。
所以问题不是"要不要持久化",而是"要什么样的持久化"——是允许丢几分钟数据换性能,还是一秒都不能丢?这决定了你选 RDB、AOF 还是混合模式。
Redis 给了两种独立的持久化机制:
- RDB(Redis Database):周期性把内存里的全部数据"拍照"存成一个快照文件
dump.rdb。 - AOF(Append Only File):把每一条写命令追加到一个日志文件
appendonly.aof里,重放就能还原数据。
两者可以单独开,也可以一起开。下面分别拆开看。
二、RDB:周期性全量快照
工作原理
RDB 的思路很直白:隔一段时间,把当前内存里的所有 key-value 整个 dump 成一个紧凑的二进制文件。这个文件就叫 dump.rdb。
触发快照的方式有几种:
- 手动命令:
SAVE(在主线程里 dump,会阻塞所有请求,生产环境绝对别用)、BGSAVE(fork 一个子进程在后台 dump,推荐)。 - 配置自动触发:在
redis.conf里用save规则定义"N 秒内发生 M 次修改就触发 BGSAVE"。 - shutdown:正常关闭 Redis 时,如果没有开启 AOF,会自动做一次 RDB dump。
- 主从复制:从节点首次连接时,主节点会触发 BGSAVE 生成 RDB 发给从节点。
配置示例
# redis.conf 中的 RDB 配置
# save 规则:以下任意一条满足就触发 BGSAVE
save 3600 1 # 3600 秒内至少 1 个 key 变化
save 300 100 # 300 秒内至少 100 个 key 变化
save 60 10000 # 60 秒内至少 10000 个 key 变化
# 想完全关闭 RDB,写成:
# save ""
# RDB 文件名和目录
dbfilename dump.rdb
dir /var/lib/redis
# 如果上一次 BGSAVE 失败,是否停止接受写请求(默认 yes,保护数据一致性)
stop-writes-on-bgsave-error yes
# 是否对 RDB 文件做 LZF 压缩(默认 yes,CPU 换磁盘空间)
rdbcompression yes
# 是否在文件头部加 CRC64 校验(默认 yes,发现文件损坏)
rdbchecksum yes
fork 子进程与 copy-on-write
BGSAVE 是怎么做到"既不阻塞主线程、又能拿到一个一致性的快照"?答案是 fork() + copy-on-write(COW):
- 主进程调用
fork()创建一个子进程。 - 子进程和父进程共享同一份物理内存页(COW 机制)。
- 子进程把共享内存里的数据遍历写入 RDB 文件。
- 父进程在此期间继续处理客户端请求。如果父进程要修改某个内存页,内核会把那个页复制一份给父进程写,子进程看到的还是旧的。
这就保证了子进程 dump 出来的是某一时刻的完整一致性快照,而主线程全程不阻塞(除了 fork 本身那一瞬间,后面会讲)。
RDB 的优缺点
优点:
- 文件紧凑。RDB 是二进制格式,LZF 压缩后体积小,方便备份、传输、跨机房复制。
- 恢复速度快。重启时直接把 RDB 加载进内存,比 AOF 重放快得多(一个 10GB 的实例,RDB 恢复可能几十秒,AOF 重放要几分钟)。
- 对主线程影响小。fork 子进程后,主线程只在写操作触发 COW 时有一点拷贝开销,读完全不受影响。
- 适合做灾难备份。可以定时把 RDB 异地存到 S3 / 对象存储。
致命缺点:
- 两次快照之间的写入会丢。比如
save 300 100,最坏情况下断电,你会丢掉最近 5 分钟的所有写入。对很多业务场景这是不可接受的。 - fork 大实例会卡。下面会专门讲。
一句话:RDB 适合做"今天某个时间点的全量备份",不适合做"一秒都不能丢"的强持久化。
三、AOF:记录每一次写命令
工作原理
AOF 的思路完全不同:把每一条改变数据的命令(SET、DEL、INCR、EXPIRE、HSET……)原封不动追加到一个日志文件里。重启时按顺序重放这些命令,就能还原出当时的数据状态。
AOF 看起来比 RDB "可靠"得多——因为每条写都落盘了。但"落盘"在操作系统层面是个很微妙的事情:write() 只是把数据写到内核的 page cache,真正写进磁盘要靠 fsync()。所以 AOF 还需要一个 fsync 策略来控制"多频繁地把日志刷到物理磁盘"。
fsync 策略:always / everysec / no
这是 AOF 最关键的配置,直接决定"断电时最多丢多少数据":
# redis.conf
appendonly yes
appendfilename "appendonly.aof"
appenddirname "appendonlydir" # Redis 7.0 起,AOF 拆成目录结构
# 三选一
appendfsync everysec
三种策略对比:
| 策略 | 行为 | 数据安全性 | 性能影响 | 生产场景 |
|---|---|---|---|---|
always | 每条写命令都 fsync | 最高,几乎不丢数据 | 最差,每条写都阻塞等磁盘 | 极度敏感的数据,少见 |
everysec | 每秒 fsync 一次 | 高,最多丢 1 秒 | 几乎无感(有后台线程做) | 生产默认推荐 |
no | 让 OS 自己决定何时 fsync | 低,可能丢几十秒 | 最好 | 纯缓存、不在乎丢数据 |
always 是个陷阱。看起来最安全,实际上每条写命令都要等磁盘 I/O 完成(一次 fsync 在机械盘上是毫秒级),QPS 直接掉一个数量级,几乎没有生产场景能扛得住。
everysec 是黄金选择。后台线程每秒做一次 fsync,主线程只是把命令 append 到 AOF buffer,性能基本无损耗,最坏情况下丢 1 秒数据——对 99% 的业务来说这个损失完全可接受。
AOF 的优缺点
优点:
- 数据安全性高。
everysec策略下最多丢 1 秒,比 RDB 丢几分钟强太多。 - 可读性好。AOF 是纯文本协议格式(RESP),出问题时可以直接
tail -f appendonly.aof看最近发生了什么,甚至手动编辑删掉错误的命令来修复(比如有人误操作FLUSHALL)。 - durability 颗粒细。不需要像 RDB 那样全量 dump,每条写就追加几行。
缺点:
- 文件比 RDB 大得多。同一个数据集,AOF 通常比 RDB 大 2-5 倍(因为记录了所有中间状态)。
- 恢复慢。重启时要逐条重放命令,对大实例是分钟级的阻塞。
- 持续写盘。AOF 是追加写,写负载高的实例磁盘 I/O 压力比 RDB 大。
一句话:AOF 解决了"丢数据"的问题,但带来了"文件大、恢复慢"的新问题。所以需要 AOF 重写。
四、AOF Rewrite:压缩膨胀的日志
AOF 是追加写的,时间长了文件会无限膨胀。比如你 INCR counter 执行了 10000 次,AOF 里就有 10000 条 INCR counter,但最终状态只是 counter = 10000——后面那 9999 条都是冗余。
AOF Rewrite(重写) 就是把当前内存里的真实数据状态,重新用最少量的命令写一份新的 AOF,把旧的膨胀日志替换掉。
触发方式
# redis.conf
# 当 AOF 文件大小比上次重写后增长 100% 时触发
auto-aof-rewrite-percentage 100
# 且 AOF 文件至少 64MB 才触发(避免小文件频繁重写)
auto-aof-rewrite-min-size 64mb
也可以手动触发:
BGREWRITEAOF:后台异步重写。CONFIG SET auto-aof-rewrite-percentage 50:动态调整阈值。
重写过程
和 BGSAVE 类似,AOF 重写也用 fork():
- 主进程 fork 出子进程。
- 子进程遍历当前内存数据,把每个 key 的当前值用一条命令(如
SET k v、HSET k f1 v1 f2 v2)写到新 AOF 文件。 - 在子进程重写期间,父进程还在处理新写请求——这些增量命令会被暂存到一个 aof_rewrite_buffer 里。
- 子进程写完后通知父进程,父进程把 buffer 里的增量命令追加到新 AOF,然后原子替换旧文件。
整个过程主线程几乎不阻塞(除了 fork 那一瞬间),但有一段时间同时存在新旧 AOF,磁盘占用会临时翻倍。
重写后 AOF 文件能缩小到原来的 1/2 到 1/5,恢复速度也快很多。但重写本身是 I/O 密集 + fork 敏感的操作,大实例要错峰执行,不能在高峰期跑。
五、混合模式:RDB + AOF(生产推荐)
到这里你可能发现一个矛盾:
- RDB 恢复快但可能丢几分钟数据;
- AOF 数据安全但文件大、恢复慢。
有没有可能"既快又安全"?Redis 4.0 起给出了答案——混合持久化(Hybrid AOF)。
思路
把 AOF 文件分成两段:
- 前半段:RDB 格式的全量快照。直接是当前内存的二进制 dump。
- 后半段:增量 AOF 命令。从快照点开始的所有写命令。
这样在 AOF Rewrite 时,子进程不是把内存数据翻译成 AOF 命令文本,而是直接生成 RDB 二进制数据写到 AOF 文件开头,然后再追加这段时间的增量命令。
恢复时:先加载 RDB 部分(快),再重放 AOF 部分(量少)。兼顾了 RDB 的恢复速度和 AOF 的数据完整性。
配置
一行配置开启(Redis 4.0 引入,5.0 起默认就是 yes):
# 开启 RDB-AOF 混合持久化
aof-use-rdb-preamble yes
# AOF 也得开
appendonly yes
appendfsync everysec
# 通常同时保留 RDB(做异地备份)
save 3600 1
save 300 100
为什么生产推荐
混合模式本质上是在做一次聪明的分工:
- 全量恢复用 RDB 速度——大实例几十秒就能起来。
- 增量补齐用 AOF——最多丢 1 秒。
- 文件大小可控——重写后的 AOF 大小约等于一份 RDB + 一小段增量。
- 运维心智简单——只关心一个文件(appendonly.aof),不用同时管理 RDB 和 AOF 的协调。
从我自己的生产经验看,Redis 5.0+ 的实例,只要开了 AOF,默认就是混合模式。这是唯一一种在恢复速度、数据安全性、文件大小三个维度都过得去的方案,也是社区共识的生产默认。
六、fork 延迟与内存:大实例的隐形杀手
无论 RDB 的 BGSAVE 还是 AOF Rewrite,都依赖 fork()。而 fork 是 Redis 大实例最容易踩坑的地方。
fork 本身的耗时
fork() 在 Linux 上是 copy-on-write 的——子进程不真的复制父进程的整个内存,只复制页表。但页表本身在大内存实例上规模惊人:
- 一个 24GB 的 Redis 实例,页表大概几十 MB。
- fork 要复制整张页表,是个 O(内存大小) 的操作。
- 实测下来,10GB 的实例 fork 一次可能要 100-300ms,30GB 的实例能到 1-2 秒。
这 1-2 秒里,主线程是被阻塞的——所有请求都在等。这就是所谓的 "fork 延迟"。对延迟敏感的业务(实时报价、风控、广告竞价),这就是 P99 抖动的元凶。
内存翻倍风险(COW 的另一面)
fork 之后父子进程共享内存页,正常情况下 COW 让两者看起来很省内存。但是:
- 如果主进程在 dump 期间持续高频写入,每一页写操作都会触发内核复制物理页。
- 极端情况下,主进程把所有页都改了一遍,物理内存占用接近翻倍。
- 这会导致 OOM——比如你给 Redis 配了 24GB 内存,跑满了,BGSAVE 时再加 24GB 的 COW 拷贝,机器总共 48GB 内存就不够了。
运维经验:
- Redis 实例的内存占用不要超过物理内存的 50-60%,给 fork 期间的 COW 留余量。
- 监控指标
info memory里的used_memory_rss,BGSAVE 期间会显著上升。 - 大实例(>20GB)可以考虑分片(Redis Cluster),单实例控制在小尺寸。
改善 fork 性能
# 关闭 transparent huge pages(THP),THP 会让 fork 慢 2-3 倍
# 在宿主机上执行:
# echo never > /sys/kernel/mm/transparent_hugepage/enabled
# 控制重写/bgsave 的触发频率,避免高峰期触发
# 通过动态 CONFIG SET 在低峰期手动触发
一句话:fork 是 Redis 大实例的"隐形税"。实例越大,越要谨慎对待 fork 频率和 COW 内存。
七、恢复速度的取舍:到底选哪个?
下面是一组典型对比(10GB 数据集,相同硬件):
| 方案 | 重启加载耗时 | 丢失数据上限 | 文件大小 | 主线程影响 |
|---|---|---|---|---|
| 纯 RDB(save 300 100) | ~30-60s | ~5 分钟 | 小 | 周期 fork |
| AOF always | 几分钟 | ~0 | 大 | 写阻塞严重 |
| AOF everysec | 几分钟 | 1 秒 | 大 | 几乎无感 |
| 混合(推荐) | ~40-90s | 1 秒 | 中 | 几乎无感 |
恢复速度的排序:RDB ≈ 混合 > AOF(纯)
数据安全性的排序:always > 混合 ≈ AOF everysec > RDB > AOF no
综合性价比:混合模式 > AOF everysec > RDB
八、选型建议:看你拿 Redis 当什么
没有"最好的方案",只有"最合适的方案"。我把生产里常见的几种用法归类一下:
场景 1:纯缓存(key 失效后回源即可)
推荐:不持久化,或者只开 RDB。
save 900 1 # 低频 RDB,仅做兜底
appendonly no
理由:缓存丢了大不了回源重建,没必要为持久化付出磁盘 I/O 和 fork 开销。如果担心冷启动太慢,开一个低频 RDB 当快照即可。
场景 2:准业务数据(Session、计数器、限流、排行榜)
推荐:AOF + everysec。
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes # 开启混合模式
save 3600 1 # 保留 RDB 做异地备份
save 300 100
理由:丢 1 秒数据业务可接受,但绝不能丢几分钟。混合模式让恢复速度也跟得上。
场景 3:核心数据存储(订单、支付、消息)
推荐:别用 Redis 当主存储。 上 MySQL / PostgreSQL / MongoDB 这类真正的持久化数据库。Redis 至多做缓存或加速层,靠 Canal/订阅 binlog 重建。
理由:Redis 的持久化是"尽力而为",不是 ACID 保证。把核心业务数据放 Redis 是架构上的错误。
场景 4:大规模实例(>20GB)
推荐:分片(Redis Cluster)+ 混合模式。
# 每个分片实例都开混合持久化
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
把单实例控制在 8-16GB,避开 fork 大实例的延迟和 COW 内存翻倍坑。同时做好主从 + 哨兵的 HA。
九、运维检查清单
上线前过一遍这些点,能避免 80% 的持久化事故:
appendfsync设的是everysec? 别用 always,也别用 no。aof-use-rdb-preamble yes? Redis 5.0+ 默认 yes,老版本要确认。- 磁盘空间监控? AOF rewrite 时临时会有两份 AOF,磁盘占用会翻倍。
stop-writes-on-bgsave-error yes? 这能保证 RDB 失败时 Redis 不再接受写,避免静默丢数据。- THP 关闭了?
echo never > /sys/kernel/mm/transparent_hugepage/enabled,否则 fork 延迟翻倍。 - 内存余量够吗?
used_memory_rss不超过物理内存 60%。 - AOF 文件损坏怎么办? 用
redis-check-aof --fix appendonly.aof修复(会截断损坏部分)。 - RDB 文件怎么校验?
redis-check-rdb dump.rdb。 - 异地备份? 定时把 RDB 上传到对象存储,应对整机房故障。
- 演练过故障恢复吗? 不演练的备份等于没备份。
十、监控指标
Prometheus + redis_exporter 里这几个指标最关键:
| 指标 | 含义 | 报警阈值 |
|---|---|---|
redis_persistence_loading | 是否正在加载 RDB/AOF | 持续 > 0 报警 |
redis_rdb_last_bgsave_status | 上次 BGSAVE 状态 | 非 OK 立即报警 |
redis_rdb_last_bgsave_time_sec | 上次 BGSAVE 耗时 | > 30s 关注 |
redis_aof_last_rewrite_time_sec | 上次 AOF 重写耗时 | > 60s 关注 |
redis_aof_pending_fsync | 待 fsync 的字节数 | 持续上涨说明磁盘 IO 瓶颈 |
redis_connected_clients | 客户端连接数 | 配合 fork 延迟一起看 |
redis_used_memory_rss / redis_used_memory | 内存碎片率 | > 1.5 关注,> 2 报警 |
redis_last_key_changes | 最近写入量 | 用来调优 save 规则 |
特别关注 redis_rdb_last_bgsave_time_sec 和 fork 频率,如果发现 fork 经常在业务高峰触发,要调高 save 的阈值,或者改成纯手动触发。
小结
写到这里,把 Redis 持久化的关键认知收一下:
- RDB 是快照,AOF 是日志,混合模式是两者结合。理解这三种机制的本质区别,比记住配置项更重要。
appendfsync everysec是生产默认。always 是性能陷阱,no 是数据豪赌。- Redis 4.0+ 的混合模式是生产首选。一份 AOF 文件,前半 RDB 全量、后半增量命令,恢复快又数据安全。
- fork 是大实例的隐形税。实例越大,fork 越慢、COW 内存翻倍风险越高——单实例控制在 8-16GB,给内存留 40% 余量。
- 选型看用途:纯缓存不用持久化,准业务用混合模式,核心数据别放 Redis。
最后送一句话做收尾——
持久化不是开关,而是承诺:你愿意为"重启不丢数据"这件事付出多少 CPU、多少磁盘、多少 fork 抖动。搞清楚这份承诺的代价,才配说"我的 Redis 是可靠的"。
Related
相关推荐
文章
Docker 部署 Next.js 社区项目的注意事项
围绕 DevOps、Next.js 的技术趋势、工程实践和独立开发视角。
文章
OpenTelemetry 如何帮助你观察 AI 应用?
围绕 DevOps、AI 的技术趋势、工程实践和独立开发视角。
文章
长运行工具调用:Operation、Poll 与 Cancel 的终态协议
初始请求超时后,工具可能仍在服务端运行;发出取消也不等于副作用已停止。本文为单次长运行工具调用建立 operation 身份、轮询水位、取消竞态和唯一终态接受点。
文章
Agent 记忆删除传播:从用户遗忘请求到可验证清除
删除主表一行不会清掉向量、摘要、缓存和恢复后的备份。本文用删除状态机、代际墓碑、派生血缘与端到端 canary 验证真正遗忘。