#DevOps

Redis 持久化:RDB vs AOF 与混合模式

Redis 是内存数据库,但数据不能只存内存——断了就没了。本文讲清两种持久化机制:RDB(快照,周期性全量 dump)、AOF(追加日志,记录每次写),各自的优劣、RDB+AOF 混合模式(生产推荐),以及 fork 延迟/AOF 重写/恢复速度的取舍。

本文要点

  • 主题聚焦:DevOps
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年2月20日·更新于 2026年7月23日

Redis 持久化:RDB vs AOF 与混合模式

Redis 把所有数据放在内存里跑,快是真快,但内存这东西——掉电就没。

所以"持久化"这件事,本质上是在回答一个问题:进程挂了、机器宕了、断电了,重启之后我还能拿回多少数据?能多快拿回来?要付出什么代价?

这篇把 Redis 的三种持久化方案——RDB、AOF、RDB+AOF 混合模式——一次讲透,并给出我在生产环境里真实的选型逻辑。


一、为什么 Redis 需要持久化

很多人对 Redis 的第一印象是"内存数据库"。这话只对了一半——Redis 的运行时确实是纯内存的,但它并不是"只活在内存里"。

为什么要持久化?三个很现实的场景:

  1. 重启不丢数据。无论是发版重启、配置变更、还是 OOM 被 kill 掉,进程一旦退出,内存里的数据就灰飞烟灭。对缓存场景来说大不了再回源重建,但对会话、消息队列、计数器、限流、排行榜这类"准业务数据",丢了就是生产事故。
  2. 宕机恢复。机器掉电、内核 panic、硬件故障,这种非优雅退出下内存数据全部丢失。如果没有持久化文件,Redis 重启后就是一个空实例。
  3. 主从全量同步。新加的从节点、断线重连的从节点,需要从主节点的 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 发给从节点。

配置示例

conf
# 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):

  1. 主进程调用 fork() 创建一个子进程。
  2. 子进程和父进程共享同一份物理内存页(COW 机制)。
  3. 子进程把共享内存里的数据遍历写入 RDB 文件。
  4. 父进程在此期间继续处理客户端请求。如果父进程要修改某个内存页,内核会把那个页复制一份给父进程写,子进程看到的还是旧的。

这就保证了子进程 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 最关键的配置,直接决定"断电时最多丢多少数据":

conf
# 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,把旧的膨胀日志替换掉。

触发方式

conf
# 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():

  1. 主进程 fork 出子进程。
  2. 子进程遍历当前内存数据,把每个 key 的当前值用一条命令(如 SET k v、HSET k f1 v1 f2 v2)写到新 AOF 文件。
  3. 在子进程重写期间,父进程还在处理新写请求——这些增量命令会被暂存到一个 aof_rewrite_buffer 里。
  4. 子进程写完后通知父进程,父进程把 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):

conf
# 开启 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 性能

conf
# 关闭 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-90s1 秒中几乎无感

恢复速度的排序:RDB ≈ 混合 > AOF(纯)

数据安全性的排序:always > 混合 ≈ AOF everysec > RDB > AOF no

综合性价比:混合模式 > AOF everysec > RDB


八、选型建议:看你拿 Redis 当什么

没有"最好的方案",只有"最合适的方案"。我把生产里常见的几种用法归类一下:

场景 1:纯缓存(key 失效后回源即可)

推荐:不持久化,或者只开 RDB。

conf
save 900 1              # 低频 RDB,仅做兜底
appendonly no

理由:缓存丢了大不了回源重建,没必要为持久化付出磁盘 I/O 和 fork 开销。如果担心冷启动太慢,开一个低频 RDB 当快照即可。

场景 2:准业务数据(Session、计数器、限流、排行榜)

推荐:AOF + everysec。

conf
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)+ 混合模式。

conf
# 每个分片实例都开混合持久化
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes

把单实例控制在 8-16GB,避开 fork 大实例的延迟和 COW 内存翻倍坑。同时做好主从 + 哨兵的 HA。


九、运维检查清单

上线前过一遍这些点,能避免 80% 的持久化事故:

  1. appendfsync 设的是 everysec? 别用 always,也别用 no。
  2. aof-use-rdb-preamble yes? Redis 5.0+ 默认 yes,老版本要确认。
  3. 磁盘空间监控? AOF rewrite 时临时会有两份 AOF,磁盘占用会翻倍。
  4. stop-writes-on-bgsave-error yes? 这能保证 RDB 失败时 Redis 不再接受写,避免静默丢数据。
  5. THP 关闭了? echo never > /sys/kernel/mm/transparent_hugepage/enabled,否则 fork 延迟翻倍。
  6. 内存余量够吗? used_memory_rss 不超过物理内存 60%。
  7. AOF 文件损坏怎么办? 用 redis-check-aof --fix appendonly.aof 修复(会截断损坏部分)。
  8. RDB 文件怎么校验? redis-check-rdb dump.rdb。
  9. 异地备份? 定时把 RDB 上传到对象存储,应对整机房故障。
  10. 演练过故障恢复吗? 不演练的备份等于没备份。

十、监控指标

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 持久化的关键认知收一下:

  1. RDB 是快照,AOF 是日志,混合模式是两者结合。理解这三种机制的本质区别,比记住配置项更重要。
  2. appendfsync everysec 是生产默认。always 是性能陷阱,no 是数据豪赌。
  3. Redis 4.0+ 的混合模式是生产首选。一份 AOF 文件,前半 RDB 全量、后半增量命令,恢复快又数据安全。
  4. fork 是大实例的隐形税。实例越大,fork 越慢、COW 内存翻倍风险越高——单实例控制在 8-16GB,给内存留 40% 余量。
  5. 选型看用途:纯缓存不用持久化,准业务用混合模式,核心数据别放 Redis。

最后送一句话做收尾——

持久化不是开关,而是承诺:你愿意为"重启不丢数据"这件事付出多少 CPU、多少磁盘、多少 fork 抖动。搞清楚这份承诺的代价,才配说"我的 Redis 是可靠的"。

Related

相关推荐