Redis 持久化 RDB vs AOF 怎么选?——数据安全 vs 性能
引言
"凌晨 Redis 主节点物理机故障,哨兵把流量切到了从节点,业务几分钟就恢复了——但早上对账发现,故障前 4 分钟内的 1200 笔已支付订单状态全部丢失。" 支付服务把订单状态临时存在 Redis 里做状态机流转,默认配置下只有 RDB、save 60 10000(60 秒内 1 万次写入才触发快照),深夜低峰期写入稀疏,最后一次成功快照停在故障前 4 分钟。更麻烦的是这些状态已经驱动了后续业务,丢了就补不回来。
另一个极端同样常见:有人被这次事故吓到,把所有实例都开成 appendfsync always,结果每条写命令都强制刷盘,单实例写入 QPS 从 5 万掉到 2000,应用线程大量阻塞在 write() 上——为了不丢 1 秒钟的数据,付出了 25 倍吞吐的代价,这显然也不是正确答案。
Redis 持久化的选型之所以争议不断,是因为它从来不是"RDB 还是 AOF"的二选一,而是一连串工程权衡:能接受丢多久的数据、文件多大、恢复多快、fork 子进程对大实例的卡顿有多大、重写期间的双写内存撑不撑得住。这篇文章从持久化的本质讲起,把 RDB 快照、AOF 追加日志、AOF 重写、混合持久化四个机制拆开讲透,最后给出按业务场景对号入座的选型表和可直接抄的生产配置(主线基于 Redis 6.2/7.x,7.0 的 Multi-Part AOF 新结构会单独说明)。先给结论:今天 95% 的生产场景标准答案是"混合持久化 + appendfsync everysec"——RDB 保证恢复快,AOF 保证最多丢 1 秒,而不是任何一个极端。
一、全景:Redis 为什么需要持久化,持久化到底在保存什么
1.1 内存数据库的命门
Redis 的所有数据在内存里,进程退出、机器宕机、电源故障,内存内容全部消失。持久化就是把内存中的数据状态以某种格式写到磁盘,重启时再加载回来。需要先建立一个认知:Redis 持久化保存的是"数据状态",但两种机制记录状态的方式完全不同:
RDB(Redis DataBase):拍快照
某一时刻内存里有什么 → 直接序列化成紧凑二进制 → dump.rdb
思路:记录"结果"
AOF(Append Only File):写日志
每执行一条写命令 → 把命令文本追加到日志 → appendonly.aof
重启时把日志里的命令从头回放一遍,重建数据
思路:记录"过程"
这一个差异决定了两者的全部特性:快照天然紧凑、恢复快,但两次快照之间的数据会丢;日志天然完整、丢失少,但文件膨胀快、恢复要逐条回放。
1.2 选型要回答的五个问题
| 问题 | RDB | AOF |
|---|---|---|
| 最坏丢多少数据 | 取决于快照间隔,默认配置可能丢几分钟 | everysec 最多约 1~2 秒 |
| 文件体积 | 小,紧凑二进制+压缩 | 大(命令文本),靠重写压缩 |
| 重启恢复速度 | 快,直接反序列化 | 慢,回放命令(大文件可能慢数倍) |
| 对在线性能的影响 | bgsave 时 fork + 写盘 | 持续写盘 + 重写时 fork |
| 文件可读性/可修复 | 二进制不可读 | 命令文本可读,redis-check-aof 可截断修复 |
二、RDB:给内存拍一张二进制快照
2.1 触发方式:save 与 bgsave 的生死区别
# ✗ save:主线程自己生成快照,期间阻塞所有请求,生产环境永远不要用
127.0.0.1:6379> SAVE
OK
# ✓ bgsave:fork 出子进程在后台生成快照,主进程继续服务
127.0.0.1:6379> BGSAVE
Background saving started
自动持久化规则触发的实际都是 bgsave:
# redis.conf:save <秒数> <写操作次数>,任一条件满足即触发 bgsave
save 3600 1 # 3600秒(1小时)内至少1次写入
save 300 100 # 300秒(5分钟)内至少100次写入
save 60 10000 # 60秒内至少10000次写入
stop-writes-on-bgsave-error yes # 快照失败后拒绝写入,防止"以为持久化正常"
rdbcompression yes # LZF压缩,省磁盘,CPU开销很小
rdbchecksum yes # 文件末尾CRC64校验
dbfilename dump.rdb
dir /data/redis # RDB和AOF文件目录,生产务必挂独立数据盘
不想用 RDB 时显式关闭(注意不是留空,留空在某些版本含义模糊):
save ""
2.2 bgsave 为什么能"边服务边备份":fork + 写时复制
这是 RDB 最核心的机制,也是理解其性能影响的关键:
主进程
│ fork() 系统调用(复制页表,不复制数据,速度取决于内存映射规模)
├───────────────── 子进程(与主进程内存映像完全一致的副本)
│ 遍历内存,把"fork 那一刻"的数据写入临时 RDB
│ 写完 fsync → 原子 rename 替换旧 dump.rdb
│
└─ 主进程继续处理写请求
写某页时,操作系统触发 COW(Copy-On-Write):
复制该物理页给主进程改新页,子进程看到的仍是旧页(快照一致性)
COW 让父子进程逻辑上各持一份内存、物理上共享未修改的页,快照期间内存增量只等于"被修改的页大小",而不是整份数据。但它也带来三个生产中必须面对的成本(第六章细讲):
- fork 本身会短暂阻塞主线程:要复制页表,10GB 实例通常几十毫秒,内存越大、页表越大,极端可达秒级。
- 快照期间大量写入会放大内存:被 COW 复制的页越多,额外内存越高(最坏接近数据集大小),大实例 + 大量写入可能触发 OOM。
- 透明大页(THP)必须关闭:开启时 COW 复制单位从 4KB 变成 2MB,写一页复制 2MB,内存放大和卡顿都会恶化。
2.3 RDB 的数据安全窗口
快照是离散的点,两次快照之间宕机,中间数据全丢。用默认规则算笔账:
低峰期(每分钟只有几十次写)
→ 永远触发不了 save 60 10000
→ 只能靠 save 300 100 / save 3600 1
→ 最坏丢失窗口接近 5 分钟~1 小时 ← 开篇事故的根因
高峰期
→ 每 60 秒左右一次快照,最坏丢约 1 分钟
另外 LASTSAVE 和 INFO persistence 可以确认最近一次成功快照的时间:
127.0.0.1:6379> INFO persistence
rdb_last_bgsave_status:ok
rdb_last_save_time:1727510400 # 最近一次RDB成功时间
rdb_last_bgsave_time_sec:3 # 上次bgsave耗时
rdb_changes_since_last_save:1832 # 上次快照后的写入数
latest_fork_usec:87231 # ★ 上次fork耗时(微秒),监控大实例必看
2.4 RDB 适合做什么
- 灾难恢复的冷备文件:紧凑、单文件,定时把 dump.rdb 传到对象存储/异地,恢复时拉回来启动即可。
- 全量同步的载体:主从全量复制时主节点生成 RDB 发给从节点(无盘复制除外)。
- 可整体重建的纯缓存:数据丢了能从数据库/上游重新预热的场景,RDB 足够。
- 不适合:丢失窗口内有无法重建的状态(支付状态机、分布式锁之外的凭证类数据)。
三、AOF:把每条写命令追加到日志
3.1 工作流程:命令执行后才写日志
AOF 的写入方向与 MySQL 的 redo log 不同,是先执行命令、再记日志:
客户端写命令
→ 主进程执行命令(数据先变)
→ 命令协议文本追加到 AOF 缓冲区(aof_buf,用户态内存)
→ 根据 appendfsync 策略刷到内核页缓存/磁盘
"先执行后记录"的好处是不会阻塞命令执行、日志里不会有语法错误的命令;代价是写完命令、还没刷盘时宕机,这部分会丢——丢多少由 fsync 策略决定。
3.2 三种刷盘策略:always / everysec / no
appendonly yes
appendfilename "appendonly.aof" # Redis 6 单文件;7.x 实际写在 appendonlydir/ 下
appendfsync everysec # ★ 三种策略的核心开关
no-appendfsync-on-rewrite no # 重写期间是否暂停fsync,见 4.4
auto-aof-rewrite-percentage 100 # 文件比上次重写后增长100%触发重写
auto-aof-rewrite-min-size 64mb # 文件至少64mb才考虑重写
aof-load-truncated yes # 恢复时遇到尾部损坏自动截断
| 策略 | 刷盘时机 | 最坏丢失 | 写入性能(参考量级) | 适用 |
|---|---|---|---|---|
always | 每条写命令都 fsync 后才返回 | 几乎为零 | 约几百~2000 QPS,性能下降一个数量级以上 | 极端数据安全、写入量极低 |
everysec(默认推荐) | 后台线程每秒 fsync 一次 | 约 1~2 秒 | 接近无持久化,2万~5万+ QPS | 绝大多数业务 |
no | 完全交给操作系统(通常约30秒) | 约 30 秒,且不可控 | 最高 | 基本没人显式使用 |
关于 everysec 一个容易忽略的实现细节:fsync 由独立后台线程每秒执行,主线程只管把命令 write 到内核缓存,所以正常情况下主线程不等磁盘;但如果某次 fsync 卡住超过 2 秒(磁盘繁忙),主线程为保证日志顺序会短暂阻塞等待——这就是 Redis 单实例上磁盘延迟可能传导到请求延迟的唯一路径。监控上看 aof_delayed_fsync 计数器:
INFO persistence
aof_enabled:1
aof_last_write_status:ok # AOF写入状态
aof_delayed_fsync:0 # ★ 大于0说明磁盘已经跟不上、主线程被拖慢
aof_last_bgrewrite_status:ok
aof_current_size:8126464 # 当前AOF大小
aof_base_size:4194304 # 上次重写后的基准大小(两者比值决定何时再重写)
3.3 AOF 恢复:回放命令
启动时若同时存在 RDB 和 AOF,Redis 优先加载 AOF(因为它的数据更完整)。AOF 回放是逐条执行写命令,同样的数据量,恢复时间通常是 RDB 的 3~10 倍(混合持久化就是为了解决这个问题,第五章讲)。
AOF 是文本协议(RESP),出问题可以直接看、可以修:
# 宕机导致文件尾部写了半条命令,先备份再用官方工具检查
cp appendonly.aof appendonly.aof.bak
redis-check-aof --fix appendonly.aof # 截断尾部不完整命令,数据损失仅限最后半条
这是 AOF 相对 RDB 的运维优势:RDB 文件结构损坏基本无法手工救,AOF 只丢尾部一点点。
四、AOF 重写:bgrewriteaof 怎么给日志"瘦身"
4.1 不重写会怎样
一个 key 被 INCR counter 了 10 万次,AOF 里就老老实实记了 10 万行 INCR;一个 key 设了又删(SET k 1 ... DEL k),日志里留下两条早已无意义的命令。AOF 文件会无限膨胀,大部分内容对"重建当前数据"毫无价值。
AOF 重写(rewrite)解决的就是这个问题:不读旧日志,直接遍历当前内存,用尽可能少的命令重新描述数据集。
旧 AOF(10万行) 重写后的 AOF(1行)
INCR counter → ...×100000 SET counter 100000
SET k 1 (k 已删除,整个key不出现在新文件)
...
DEL k
4.2 重写流程:fork 子进程 + 重写缓冲区双写
主进程收到 BGREWRITEAOF(或自动触发)
│ fork() 子进程
│
├─ 子进程:遍历内存数据集
│ 生成描述当前状态的最少命令 → 写临时 AOF 文件
│
└─ 主进程继续服务,重写期间的新写命令同时写两处:
① 旧 AOF 缓冲区(保证重写失败时旧日志完整)
② aof_rewrite_buf(重写缓冲区,记录重写期间增量)
子进程写完
→ 主进程短暂阻塞(毫秒级):把 rewrite 缓冲区的增量追加到新文件
→ fsync → 原子 rename 新文件替换旧 AOF
关键点:重写不是对旧文件做压缩,而是对内存做一次全新快照式转储,所以与旧 AOF 当前多大无关,只与数据集大小有关。主进程最后的"追加增量 + rename"是全程唯一的短暂阻塞点。
4.3 自动触发参数
# 当前AOF大小比"上次重写完成时的大小"增长 100%(即翻倍)且 ≥64MB 时自动重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
刚启动没有"基准大小"时,Redis 以 AOF 文件的初始加载大小为基准。也可以手动触发:
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
bgsave 和 bgrewriteaof 不会同时执行:如果一个正在跑,另一个会排队,避免双倍 COW 内存。这也是监控大实例持久化任务时要关注的点(Redis 6.2+ 对两者调度有优化,但仍互相影响节奏)。
4.4 重写期间的两个参数取舍
# 重写期间主进程是否对旧AOF停止fsync
no-appendfsync-on-rewrite no
no(默认):重写期间照常每秒 fsync,数据安全,但重写 IO + fsync IO 叠加,磁盘繁忙时可能拖慢主进程。yes:重写期间不 fsync,减轻磁盘压力,但重写的几十秒~几分钟内宕机会丢较多数据。除非磁盘 IO 确实扛不住,否则保持 no。
内存账也要提前算:重写期间的"新 AOF 文件 + 旧 AOF 文件 + rewrite 缓冲区"三份数据共存,加上 fork 的 COW,磁盘至少预留数据集 2 倍以上空间,内存预留 20%~50% 余量,否则重写高峰可能写满磁盘或触发 OOM。
五、混合持久化:RDB 的头 + AOF 的尾
5.1 它解决了 AOF 恢复慢的痛点
Redis 4.0 引入、5.0 默认开启的混合持久化(aof-use-rdb-preamble),让 AOF 重写生成的新文件不再是纯命令文本,而是"RDB 格式的全量底座 + 一小段 AOF 增量命令":
混合 AOF 文件结构
┌──────────────────────────────────────┐
│ RDB 二进制:重写时刻的完整数据集快照 │ ← 占文件99%体积,加载走快速反序列化
├──────────────────────────────────────┤
│ AOF 命令:重写之后的增量写命令 │ ← 只有很短的尾部,逐条回放
└──────────────────────────────────────┘
配置(Redis 5.0+ 默认就是 yes,建议显式写出):
aof-use-rdb-preamble yes
加载时:前半段当 RDB 快速反序列化,尾部 AOF 增量回放。既拿到了 AOF 的低丢失(最多 1 秒),又拿到了 RDB 的快恢复——一个 10GB 数据的实例,纯 AOF 恢复可能要十几分钟,混合格式通常一两分钟。
5.2 Redis 7.0 的 Multi-Part AOF(MP-AOF)
Redis 7.0 把单个大 AOF 文件拆成了目录下的多文件结构,混合格式成为基础形态,重写不再需要"暂停追加增量"的旧机制:
dir/appendonlydir/
├── appendonly.aof.manifest # 清单:记录当前有效文件集合
├── appendonly.aof.1.base.rdb # BASE:RDB全量底座(混合持久化)
├── appendonly.aof.1.incr.aof # INCR:base之后的增量命令,持续追加
└── (重写后旧版本进入 history,安全保留后清理)
- 重写时生成新版本的 base,旧的 base+incr 被标记为 history,新 incr 从头开始——新旧版本在磁盘上并存,通过 manifest 原子切换,杜绝了旧版"重写中途崩溃可能损坏唯一 AOF"的风险。
- 7.0 后
appendfilename仍是逻辑前缀,实际文件都在appendonlydir/目录;运维备份要备份整个目录和 manifest,不能只拷单个文件。 - Redis 8.x 延续这套结构并增强了 I/O 线程与副本侧持久化能力,配置语义不变。
六、大实例的持久化成本:fork 卡顿怎么治
RDB 和 AOF 重写都要 fork,在数据量小的时候无感,实例到了 10GB+ 就成了头号性能问题。理解成本构成后,治理手段很明确。
6.1 fork 耗时看什么
INFO persistence
latest_fork_usec:87231 # 最近一次fork耗时,单位微秒。87231 ≈ 87ms
经验阈值:稳定低于 100ms 健康;持续超过 200ms 需要治理;超过 1s 的大实例必须拆分或上无盘方案。 fork 耗时主要与内存映射的页表规模相关,也和机器内存碎片化有关。
6.2 治理清单
| 措施 | 说明 |
|---|---|
| 关闭 THP | echo never > /sys/kernel/mm/transparent_hugepage/enabled,写进 rc.local/启动脚本;否则 COW 以 2MB 为单位,内存放大数百倍 |
| 允许内存过量分配 | sysctl vm.overcommit_memory=1,保证 fork 不会因"看似内存不足"失败(Redis 官方明确要求) |
| 预留 COW 内存 | 持久化实例内存占用不超过物理内存的 50%~60%,给快照期间写入留复制空间 |
| 控制单实例大小 | 业务实例建议 ≤ 10~20GB;更大的数据做分片(Cluster),fork 耗时随内存近似线性增长 |
| 错峰持久化 | 多个大实例在同一台物理机时,错开 bgsave/重写时间,避免 fork 和 IO 叠加 |
| 用更快的磁盘 | AOF everysec 的 fsync 延迟直接受磁盘影响,NVMe SSD 是标配;aof_delayed_fsync 要盯住 |
| 监控 | latest_fork_usec、rdb_changes_since_last_save、aof 重写耗时、磁盘 inode/容量 |
6.3 从节点的特殊用法
持久化任务放在从节点也是常见架构:主节点关闭 RDB/AOF(追求最低延迟),让一个专用从节点开混合持久化负责备份,并把多个从节点的持久化调度时间错开,避免 fork 和磁盘 IO 叠加。代价是故障切换后新主没有本地持久化文件——所以至少要保留一个开启持久化的副本,且冷备文件定时上传对象存储,不能让整个拓扑在持久化上裸奔。
七、选型决策:四种典型场景对号入座
你的 Redis 数据丢了会怎样?
│
├─ 完全无所谓,能从MySQL/上游全量重建(纯缓存、热点数据、排行榜临时值)
│ → 关闭持久化(save "" / appendonly no),或仅保留低频 RDB 做快速预热
│ 配合主从副本防单点故障即可
│
├─ 丢几秒可以接受,但不能丢几分钟~几小时(绝大多数业务:会话、状态机、限流计数)
│ → ★ 混合持久化:appendonly yes + everysec + aof-use-rdb-preamble yes
│ 同时保留 RDB 定时冷备(默认 save 规则即可)上传异地
│
├─ 一条都不能丢(资金凭证、无法重建的审计状态)
│ → 先反问:这种数据为什么在 Redis?应迁到数据库/带事务的存储
│ 确需保留:everysec + 多副本 + wait 命令确认写入副本数;
│ always 只用于极低写入量场景,做好压测
│
└─ 跨机房容灾/长期归档需求
→ 定时 bgsave 产物 + AOF 目录定期上传对象存储(RDB单文件最适合冷备)
主从/Cluster 解决高可用,文件备份解决灾难恢复,两者不互相替代
完整生产配置(混合持久化基线,Redis 6.2/7.x 通用)
# ===== RDB:保留快照,作为冷备与全量同步载体 =====
save 3600 1
save 300 100
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
# ===== AOF:数据安全主角 =====
appendonly yes
# Redis 7:appendfilename 是逻辑名,文件落在 appendonlydir/ 目录
appendfilename "appendonly.aof"
appendfsync everysec
aof-use-rdb-preamble yes # 混合持久化(5.0默认yes,建议显式)
# ===== 重写策略 =====
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
no-appendfsync-on-rewrite no # 磁盘不是极端紧张就保持no
aof-load-truncated yes
# ===== 目录:独立数据盘 =====
dir /data/redis
三种持久化形态总对比
| 维度 | 仅 RDB | 仅 AOF(everysec) | 混合持久化(推荐) |
|---|---|---|---|
| 最坏丢失 | 分钟级(低峰更久) | 1~2 秒 | 1~2 秒 |
| 文件大小 | 最小 | 最大(重写后仍含命令文本) | 较小(RDB 压缩底座) |
| 恢复速度 | 最快 | 最慢(回放命令) | 接近 RDB |
| 文件可修复性 | 差 | 好(文本截断) | 底座不可读,尾部可截断 |
| 在线性能影响 | fork + COW | fsync + 重写 fork | 两者都有,但频率/体积更优 |
| 典型用途 | 冷备、全量复制、可重建缓存 | 历史兼容/纯命令日志需求 | 生产默认形态 |
八、常见问题
8.1 RDB 和 AOF 同时开启,重启时加载哪个?
优先加载 AOF(混合格式时加载混合 AOF),因为它的数据更完整;AOF 关闭或不存在时才加载 dump.rdb。所以运维要注意:你以为关了 AOF 就回到 RDB 数据,但 appendonly yes 的状态没变,旧 AOF 文件还在时重启仍会加载它。切换持久化形态后要同步处理数据目录里的旧文件,并做好备份。
8.2 everysec 真的最多丢 1 秒吗?
标称是"最近 1 秒",工程上按 1~2 秒预期:宕机发生在 fsync 前的临界时刻、或 fsync 因磁盘繁忙延迟(aof_delayed_fsync > 0),丢失窗口会略大于 1 秒。注意它保证的是"日志落盘的时间差",不是业务一致性——涉及多 key 的操作如果没用事务/Lua,回放后可能停在部分完成状态,数据一致性仍由业务设计保证。
8.3 为什么 bgsave/重写时内存涨了很多甚至 OOM?
fork 后父子共享物理页,主进程每改一页 COW 复制一页。大实例在快照期间如果有大量写入(如全量过期淘汰、批量导入),复制内存可能接近数据集大小;开启 THP 时按 2MB 大页复制,放大更严重。治理:关 THP、实例内存用到物理内存 60% 以下、大写入任务避开持久化窗口、latest_fork_usec 和内存曲线一起监控。
8.4 AOF 文件一直不重写/重写太频繁怎么办?
不重写检查三件事:① 文件是否达到 auto-aof-rewrite-min-size(默认 64MB);② 增长比例是否达到 auto-aof-rewrite-percentage;③ 是否已有 bgsave/重写在跑(互斥排队)。重写太频繁通常是 percentage 太小或写入模式产生大量冗余命令(如高频 INCR/EXPIRE 刷新),可把百分比调到 200,并用 INFO 里的 aof_current_size 与 aof_base_size 比值观察真实增长曲线,不要拍脑袋调。
8.5 主从架构下,持久化应该放在主还是从?
高可用与持久化是两件事:主从复制防的是节点宕机,RDB/AOF 防的是整个数据集损坏/误删(FLUSHALL、逻辑错误会同步到所有从节点!)。推荐组合:主从都开混合持久化或至少主开 AOF;再安排定时 bgsave + 异地冷备防逻辑误删;让一个延迟不敏感的从节点错峰承担持久化 IO 可以降低主节点抖动,但不能让全集群无持久化。另外善用 rename-command 在生产禁用/改名 FLUSHALL、FLUSHDB、CONFIG、KEYS 等危险命令。
8.6 持久化能替代数据库吗?
不能。Redis 持久化是"崩溃恢复"机制,不是数据库的 ACID 保障:everysec 有丢失窗口、RDB 有分钟级窗口、AOF 回放不保证跨 key 事务一致性、备份频率决定 RPO。正确姿势是"能重建的走缓存,丢不得的落数据库,Redis 只存加速用的副本或可容忍秒级丢失的运行态",开篇那种把无法重建的支付状态只放 Redis 的设计,再好的持久化配置也只是缓解,不是根治。
九、总结
机制速查卡
【RDB 快照】记录"结果"
触发:BGSAVE(fork子进程,生产禁用SAVE)/ save规则 / shutdown / 全量复制
原理:fork + COW,子进程转储内存为紧凑二进制 dump.rdb
优点:文件小、恢复快、适合冷备和全量同步
弱点:离散快照,默认规则最坏丢几分钟~1小时
监控:rdb_last_bgsave_status、rdb_changes_since_last_save、latest_fork_usec
【AOF 日志】记录"过程"
流程:命令先执行 → 追加 aof_buf → 按策略 fsync
always:逐条刷盘,几乎不丢,QPS 降一个数量级
everysec(推荐):后台线程每秒刷盘,最坏丢1~2秒,性能接近全开
no:交给OS,约30秒且不可控
优点:丢失少、文本可读、redis-check-aof 可截断修复
弱点:文件大、恢复慢(逐条回放)
【AOF 重写】遍历内存生成最少命令,与旧日志无关
触发:BGREWRITEAOF / 增长100%且≥64MB
流程:fork子进程转储 + 主进程双写rewrite缓冲区 → 追加增量 → 原子替换
bgsave 与 bgrewriteaof 互斥;磁盘留2倍、内存留余量
【混合持久化 4.0+ / 5.0默认】aof-use-rdb-preamble yes
AOF文件 = RDB全量底座 + 少量AOF增量 → 恢复快 + 丢失少,生产默认形态
Redis 7.0 MP-AOF:appendonlydir/ 下 base.rdb + incr.aof + manifest,
新旧版本并存原子切换,备份要备份整个目录
【大实例治理】
关 THP;vm.overcommit_memory=1;单实例≤10~20GB;
latest_fork_usec <100ms;NVMe盘;盯 aof_delayed_fsync
给团队的建议
| 项 | 建议 |
|---|---|
| 默认形态 | 混合持久化 + everysec,除非能明确论证数据可全量重建 |
| 纯缓存 | 可关 AOF,但保留低频 RDB 加速重启预热,冷备上传对象存储 |
| 操作系统 | THP=never、overcommit=1、独立数据盘、预留 COW 内存 |
| 容量规划 | 磁盘 ≥ 数据集 3 倍(新旧 AOF + RDB 共存),内存使用 ≤60% |
| 监控告警 | latest_fork_usec、aof_delayed_fsync、持久化失败状态、磁盘空间 |
| 安全 | rename-command 禁用 FLUSHALL/FLUSHDB;记住复制不防误删,冷备才防 |
| 升级 7.x | 备份对象从单文件变为 appendonlydir 整个目录,运维脚本要同步改 |
一句话
RDB 和 AOF 的全部差异都源自一个哲学分野:RDB 记录"某一刻内存里的结果",用 fork + 写时复制把内存转储成紧凑二进制快照,所以文件小、恢复快,适合冷备和主从全量同步,但离散快照之间天然有分钟级丢失窗口;AOF 记录"数据变成这样的过程",命令先执行后追加、每秒由后台线程 fsync,所以最坏只丢 1~2 秒且文本可修复,但日志无限膨胀、恢复要逐条回放;bgrewriteaof 的精妙之处在于它不压缩旧日志而是 fork 子进程遍历当前内存、用最少命令重新描述数据集,重写期间主进程靠 rewrite 缓冲区双写保住增量,最后原子替换;混合持久化则是把两者拼起来——AOF 文件以 RDB 二进制做全量底座、只留尾部一小段增量命令,重启时大部分走快速反序列化、小部分回放,同时拿到了 RDB 的快恢复和 AOF 的低丢失,Redis 7.0 的 Multi-Part AOF 又把它演进成 base.rdb + incr.aof + manifest 的目录化原子切换结构。选型时不要在 always 和关闭持久化两个极端之间摇摆:能整体重建的纯缓存用低频 RDB 甚至不持久化、丢不得的业务数据本来就不该只放 Redis,而 95% 的生产场景答案明确——混合持久化加 appendfsync everysec,再配上关闭 THP、overcommit=1、单实例不超过 20GB、latest_fork_usec 和 aof_delayed_fsync 监控、以及定时上传异地的 RDB 冷备;数据安全与性能不是二选一,理解每种机制把成本花在了哪里(fork、COW、fsync、重写双写),才能用 1 秒丢失窗口的代价换回几乎无损的吞吐。
互动话题:你们线上 Redis 开的是 RDB、AOF 还是混合持久化?有没有遇到过 fork 卡顿导致请求抖动、或者 AOF 重写把磁盘写满的情况?支付/订单状态这类"丢不得又想放 Redis"的数据,你们最终是怎么设计的?评论区聊聊你的持久化配置和踩坑经历。
参考资料
- Redis 官方文档:Redis Persistence(RDB/AOF/混合持久化)
- Redis 官方文档:Multi Part AOF(7.0 新结构)
- Redis 配置示例 redis.conf(appendfsync / rewrite 参数注释)
- Redis 管理:latency 与 fork 延迟排查(latest_fork_usec)
- Redis 官方 FAQ:Linux THP 与 overcommit 要求
标题:Redis 持久化 RDB vs AOF 怎么选?——数据安全 vs 性能
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/10/05/1791036172592.html
公众号:服务端技术精选
- 引言
- 一、全景:Redis 为什么需要持久化,持久化到底在保存什么
- 1.1 内存数据库的命门
- 1.2 选型要回答的五个问题
- 二、RDB:给内存拍一张二进制快照
- 2.1 触发方式:save 与 bgsave 的生死区别
- 2.2 bgsave 为什么能"边服务边备份":fork + 写时复制
- 2.3 RDB 的数据安全窗口
- 2.4 RDB 适合做什么
- 三、AOF:把每条写命令追加到日志
- 3.1 工作流程:命令执行后才写日志
- 3.2 三种刷盘策略:always / everysec / no
- 3.3 AOF 恢复:回放命令
- 四、AOF 重写:bgrewriteaof 怎么给日志"瘦身"
- 4.1 不重写会怎样
- 4.2 重写流程:fork 子进程 + 重写缓冲区双写
- 4.3 自动触发参数
- 4.4 重写期间的两个参数取舍
- 五、混合持久化:RDB 的头 + AOF 的尾
- 5.1 它解决了 AOF 恢复慢的痛点
- 5.2 Redis 7.0 的 Multi-Part AOF(MP-AOF)
- 六、大实例的持久化成本:fork 卡顿怎么治
- 6.1 fork 耗时看什么
- 6.2 治理清单
- 6.3 从节点的特殊用法
- 七、选型决策:四种典型场景对号入座
- 完整生产配置(混合持久化基线,Redis 6.2/7.x 通用)
- 三种持久化形态总对比
- 八、常见问题
- 8.1 RDB 和 AOF 同时开启,重启时加载哪个?
- 8.2 everysec 真的最多丢 1 秒吗?
- 8.3 为什么 bgsave/重写时内存涨了很多甚至 OOM?
- 8.4 AOF 文件一直不重写/重写太频繁怎么办?
- 8.5 主从架构下,持久化应该放在主还是从?
- 8.6 持久化能替代数据库吗?
- 九、总结
- 机制速查卡
- 给团队的建议
- 一句话
- 参考资料
评论