Redis 从入门到精通(三):持久化机制 —— RDB 与 AOF 深度解析

Redis 从入门到精通(三):持久化机制 ------ RDB 与 AOF 深度解析


一、为什么需要持久化?先理解 Redis 的数据分层

Redis 的数据存在内存中,这是它快的原因,也是它脆弱的根源。一旦进程崩溃或服务器断电,所有数据瞬间消失。

持久化的核心矛盾

复制代码
高性能(写内存) ⇄ 数据安全(写磁盘)

Redis 的解法:两者都要,但不追求实时同步

Redis 提供了三种持久化策略,各自解决不同场景的问题:

策略 一句话描述 数据安全性 性能影响
RDB 定时快照,把某一时刻的内存数据全量写入磁盘 可能丢几分钟数据 低(fork 子进程后台写)
AOF 记录每次写命令,重启时回放 最多丢 1 秒(可配) 中(取决于 fsync 策略)
混合持久化 RDB 快照 + 增量 AOF 合二为一 兼顾恢复速度与数据安全

二、RDB(Redis Database):内存快照的艺术

2.1 RDB 是什么?

RDB 就是在某个时间点 把 Redis 中所有数据生成一个二进制压缩文件(默认 dump.rdb),等同于给内存拍了一张照片。恢复时直接把这个文件加载回内存,速度极快。

2.2 触发方式

RDB 有两种触发方式:

① 手动触发

bash 复制代码
SAVE       # 主线程执行,阻塞所有客户端请求,生产禁用
BGSAVE     # fork 子进程执行,主线程继续处理请求,生产唯一选择

② 自动触发(通过配置)

bash 复制代码
# redis.conf ------ save 配置格式:save <seconds> <changes>
save 900 1      # 900 秒内至少 1 次修改 → 触发 BGSAVE
save 300 10     # 300 秒内至少 10 次修改 → 触发 BGSAVE
save 60 10000   # 60 秒内至少 10000 次修改 → 触发 BGSAVE

# 关闭自动 RDB
save ""

这三个条件是 的关系,满足任意一个就触发。配置的含义是:在保证性能的前提下,尽量减少数据丢失------写入频繁时就多快照,写入稀疏时就少快照。

2.3 BGSAVE 的完整流程

#mermaid-svg-hpz5IIqGdhHYbCet{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hpz5IIqGdhHYbCet .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hpz5IIqGdhHYbCet .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hpz5IIqGdhHYbCet .error-icon{fill:#552222;}#mermaid-svg-hpz5IIqGdhHYbCet .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hpz5IIqGdhHYbCet .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hpz5IIqGdhHYbCet .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hpz5IIqGdhHYbCet .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hpz5IIqGdhHYbCet .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hpz5IIqGdhHYbCet .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hpz5IIqGdhHYbCet .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hpz5IIqGdhHYbCet .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hpz5IIqGdhHYbCet .marker.cross{stroke:#333333;}#mermaid-svg-hpz5IIqGdhHYbCet svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hpz5IIqGdhHYbCet p{margin:0;}#mermaid-svg-hpz5IIqGdhHYbCet .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hpz5IIqGdhHYbCet .cluster-label text{fill:#333;}#mermaid-svg-hpz5IIqGdhHYbCet .cluster-label span{color:#333;}#mermaid-svg-hpz5IIqGdhHYbCet .cluster-label span p{background-color:transparent;}#mermaid-svg-hpz5IIqGdhHYbCet .label text,#mermaid-svg-hpz5IIqGdhHYbCet span{fill:#333;color:#333;}#mermaid-svg-hpz5IIqGdhHYbCet .node rect,#mermaid-svg-hpz5IIqGdhHYbCet .node circle,#mermaid-svg-hpz5IIqGdhHYbCet .node ellipse,#mermaid-svg-hpz5IIqGdhHYbCet .node polygon,#mermaid-svg-hpz5IIqGdhHYbCet .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hpz5IIqGdhHYbCet .rough-node .label text,#mermaid-svg-hpz5IIqGdhHYbCet .node .label text,#mermaid-svg-hpz5IIqGdhHYbCet .image-shape .label,#mermaid-svg-hpz5IIqGdhHYbCet .icon-shape .label{text-anchor:middle;}#mermaid-svg-hpz5IIqGdhHYbCet .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hpz5IIqGdhHYbCet .rough-node .label,#mermaid-svg-hpz5IIqGdhHYbCet .node .label,#mermaid-svg-hpz5IIqGdhHYbCet .image-shape .label,#mermaid-svg-hpz5IIqGdhHYbCet .icon-shape .label{text-align:center;}#mermaid-svg-hpz5IIqGdhHYbCet .node.clickable{cursor:pointer;}#mermaid-svg-hpz5IIqGdhHYbCet .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hpz5IIqGdhHYbCet .arrowheadPath{fill:#333333;}#mermaid-svg-hpz5IIqGdhHYbCet .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hpz5IIqGdhHYbCet .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hpz5IIqGdhHYbCet .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hpz5IIqGdhHYbCet .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hpz5IIqGdhHYbCet .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hpz5IIqGdhHYbCet .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hpz5IIqGdhHYbCet .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hpz5IIqGdhHYbCet .cluster text{fill:#333;}#mermaid-svg-hpz5IIqGdhHYbCet .cluster span{color:#333;}#mermaid-svg-hpz5IIqGdhHYbCet div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hpz5IIqGdhHYbCet .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hpz5IIqGdhHYbCet rect.text{fill:none;stroke-width:0;}#mermaid-svg-hpz5IIqGdhHYbCet .icon-shape,#mermaid-svg-hpz5IIqGdhHYbCet .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hpz5IIqGdhHYbCet .icon-shape p,#mermaid-svg-hpz5IIqGdhHYbCet .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hpz5IIqGdhHYbCet .icon-shape .label rect,#mermaid-svg-hpz5IIqGdhHYbCet .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hpz5IIqGdhHYbCet .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hpz5IIqGdhHYbCet .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hpz5IIqGdhHYbCet :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 成功
失败
客户端发起 BGSAVE
主进程 fork 子进程
fork 是否成功?
子进程:遍历数据库

将数据写入临时 RDB 文件
返回错误

(通常因为内存不足)
子进程:写入完成

原子 rename 替换旧 RDB
子进程退出

主进程收到 SIGCHLD
BGSAVE 完成
主进程:继续处理客户端请求
主进程:写操作触发 Copy-On-Write
主进程:修改的内存页

由 OS 复制一份给子进程

关键点解析

  1. fork 操作fork() 创建子进程,子进程拥有父进程的内存快照(通过页表共享同一块物理内存)。fork 本身是阻塞的,但通常很快(几毫秒到几十毫秒,取决于内存大小和系统负载)。

  2. Copy-On-Write(COW):fork 之后,父子进程共享同一块物理内存。当父进程(Redis 主线程)修改数据时,操作系统会把被修改的内存页复制一份,父进程修改副本,子进程继续读原来的页。这就是为什么 BGSAVE 期间 Redis 还能正常处理写请求。

  3. COW 的内存风险 :如果 BGSAVE 期间写入量巨大,大量内存页被复制,可能导致内存使用量翻倍。这是生产环境最需要关注的点------预留足够内存,或者避免在写入高峰期做 BGSAVE。

2.4 RDB 文件结构

RDB 文件是一个紧凑的二进制格式:

复制代码
REDIS  (魔数,5 字节)
├── RDB_VERSION (4 字节,版本号)
├── AUX_FIELDS   (可选,如 redis-ver、redis-bits 等)
├── database 0
│   ├── SELECTDB (1 字节标记)
│   ├── db_number (变长编码)
│   ├── RESIZEDB (可选,哈希表大小提示)
│   └── key-value pairs
│       ├── EXPIRETIME_MS (可选,过期时间)
│       ├── value_type (1 字节)
│       ├── key (字符串)
│       └── value (根据类型编码)
├── database 1
│   └── ...
├── EOF (1 字节,0xFF)
└── CRC64 (8 字节,校验和)

RDB 的压缩 :Redis 默认使用 LZF 算法压缩字符串类型的 value(rdbcompression yes),文本数据(JSON、XML)压缩率很高,但二进制数据(图片、序列化对象)效果有限。可通过 rdbcompression no 关闭,牺牲磁盘空间换取更少的 CPU 消耗。

2.5 RDB 的优缺点

优点 缺点
文件紧凑,适合备份和灾难恢复 两次快照之间的数据可能丢失
恢复速度快(直接加载到内存) fork 操作在大内存下可能耗时较长
对主线程性能影响小(子进程写盘) COW 可能导致内存膨胀
适合冷备、异地容灾 不支持秒级持久化

三、AOF(Append Only File):每条写命令都不放过

3.1 AOF 是什么?

AOF 记录的是所有修改 Redis 数据的写命令(以 RESP 协议文本格式追加写入)。重启时,Redis 按顺序回放 AOF 文件中的命令,重建整个数据集。

复制代码
# AOF 文件示例(appendonly.aof)
*2\r
$6\r
SELECT\r
$1\r
0\r
        # SELECT 0
*3\r
$3\r
SET\r
$3\r
key\r
$5\r
value\r
  # SET key value
*3\r
$4\r
INCR\r
$7\r
counter\r
          # INCR counter

3.2 AOF 的写入流程 ------ 三部曲

#mermaid-svg-Nlq9yvFFCJscB9Mw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Nlq9yvFFCJscB9Mw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Nlq9yvFFCJscB9Mw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Nlq9yvFFCJscB9Mw .error-icon{fill:#552222;}#mermaid-svg-Nlq9yvFFCJscB9Mw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Nlq9yvFFCJscB9Mw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Nlq9yvFFCJscB9Mw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Nlq9yvFFCJscB9Mw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Nlq9yvFFCJscB9Mw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Nlq9yvFFCJscB9Mw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Nlq9yvFFCJscB9Mw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Nlq9yvFFCJscB9Mw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Nlq9yvFFCJscB9Mw .marker.cross{stroke:#333333;}#mermaid-svg-Nlq9yvFFCJscB9Mw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Nlq9yvFFCJscB9Mw p{margin:0;}#mermaid-svg-Nlq9yvFFCJscB9Mw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Nlq9yvFFCJscB9Mw .cluster-label text{fill:#333;}#mermaid-svg-Nlq9yvFFCJscB9Mw .cluster-label span{color:#333;}#mermaid-svg-Nlq9yvFFCJscB9Mw .cluster-label span p{background-color:transparent;}#mermaid-svg-Nlq9yvFFCJscB9Mw .label text,#mermaid-svg-Nlq9yvFFCJscB9Mw span{fill:#333;color:#333;}#mermaid-svg-Nlq9yvFFCJscB9Mw .node rect,#mermaid-svg-Nlq9yvFFCJscB9Mw .node circle,#mermaid-svg-Nlq9yvFFCJscB9Mw .node ellipse,#mermaid-svg-Nlq9yvFFCJscB9Mw .node polygon,#mermaid-svg-Nlq9yvFFCJscB9Mw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Nlq9yvFFCJscB9Mw .rough-node .label text,#mermaid-svg-Nlq9yvFFCJscB9Mw .node .label text,#mermaid-svg-Nlq9yvFFCJscB9Mw .image-shape .label,#mermaid-svg-Nlq9yvFFCJscB9Mw .icon-shape .label{text-anchor:middle;}#mermaid-svg-Nlq9yvFFCJscB9Mw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Nlq9yvFFCJscB9Mw .rough-node .label,#mermaid-svg-Nlq9yvFFCJscB9Mw .node .label,#mermaid-svg-Nlq9yvFFCJscB9Mw .image-shape .label,#mermaid-svg-Nlq9yvFFCJscB9Mw .icon-shape .label{text-align:center;}#mermaid-svg-Nlq9yvFFCJscB9Mw .node.clickable{cursor:pointer;}#mermaid-svg-Nlq9yvFFCJscB9Mw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Nlq9yvFFCJscB9Mw .arrowheadPath{fill:#333333;}#mermaid-svg-Nlq9yvFFCJscB9Mw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Nlq9yvFFCJscB9Mw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Nlq9yvFFCJscB9Mw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Nlq9yvFFCJscB9Mw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Nlq9yvFFCJscB9Mw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Nlq9yvFFCJscB9Mw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Nlq9yvFFCJscB9Mw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Nlq9yvFFCJscB9Mw .cluster text{fill:#333;}#mermaid-svg-Nlq9yvFFCJscB9Mw .cluster span{color:#333;}#mermaid-svg-Nlq9yvFFCJscB9Mw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Nlq9yvFFCJscB9Mw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Nlq9yvFFCJscB9Mw rect.text{fill:none;stroke-width:0;}#mermaid-svg-Nlq9yvFFCJscB9Mw .icon-shape,#mermaid-svg-Nlq9yvFFCJscB9Mw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Nlq9yvFFCJscB9Mw .icon-shape p,#mermaid-svg-Nlq9yvFFCJscB9Mw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Nlq9yvFFCJscB9Mw .icon-shape .label rect,#mermaid-svg-Nlq9yvFFCJscB9Mw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Nlq9yvFFCJscB9Mw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Nlq9yvFFCJscB9Mw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Nlq9yvFFCJscB9Mw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. 命令执行

写入 AOF 缓冲区

(aof_buf)
2. 缓冲区 → 内核

write() 系统调用

数据进入 OS page cache
3. 内核 → 磁盘

fsync() 系统调用

真正落盘

步骤 位置 风险
命令写入 aof_buf Redis 进程内存 进程崩溃就丢失
write() 到 page cache 操作系统内核缓冲区 机器断电就丢失
fsync() 到磁盘 物理磁盘 磁盘损坏才丢失

3.3 fsync 策略:AOF 的性能与安全博弈

Redis 通过 appendfsync 配置控制第三步的触发频率:

策略 行为 数据安全 性能 推荐场景
always 每条命令都 fsync 最高,基本不丢数据 最差(磁盘瓶颈) 对数据安全性要求极致的场景
everysec 每秒 fsync 一次 最多丢 1 秒数据 较好(默认,推荐) 99% 的生产环境
no 不主动 fsync,由 OS 决定 不可控(通常 30 秒) 最好 纯缓存,数据可丢

everysec 的实现细节 :Redis 启动一个后台线程,每秒检查一次,如果距离上次 fsync 超过 1 秒,就执行 fsync。这里的精妙之处在于------它是一个独立的 fsync 线程,不会阻塞主线程处理命令。即使磁盘 IO 慢,也只是 fsync 线程被阻塞,客户端请求照常处理。

3.4 AOF 重写(Rewrite):给 AOF 文件瘦身

AOF 文件会随着时间无限增长。一个计数器 INCR counter 执行 100 万次,AOF 文件里会有 100 万条 INCR 命令,但实际上只需要一条 SET counter 1000000 就够了。

重写的目的:用最少命令重建当前数据集,消除冗余。

bash 复制代码
# 原始 AOF 文件(冗余)
SET counter 1
INCR counter     # × 99999 次
# 文件大小:~5MB

# 重写后的 AOF 文件(精简)
SET counter 100000
# 文件大小:~30 字节

AOF 重写也是通过 BGREWRITEAOF + 子进程完成的,流程与 BGSAVE 类似:
#mermaid-svg-Epg8muNqTerClcEJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Epg8muNqTerClcEJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Epg8muNqTerClcEJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Epg8muNqTerClcEJ .error-icon{fill:#552222;}#mermaid-svg-Epg8muNqTerClcEJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Epg8muNqTerClcEJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Epg8muNqTerClcEJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Epg8muNqTerClcEJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Epg8muNqTerClcEJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Epg8muNqTerClcEJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Epg8muNqTerClcEJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Epg8muNqTerClcEJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Epg8muNqTerClcEJ .marker.cross{stroke:#333333;}#mermaid-svg-Epg8muNqTerClcEJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Epg8muNqTerClcEJ p{margin:0;}#mermaid-svg-Epg8muNqTerClcEJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Epg8muNqTerClcEJ .cluster-label text{fill:#333;}#mermaid-svg-Epg8muNqTerClcEJ .cluster-label span{color:#333;}#mermaid-svg-Epg8muNqTerClcEJ .cluster-label span p{background-color:transparent;}#mermaid-svg-Epg8muNqTerClcEJ .label text,#mermaid-svg-Epg8muNqTerClcEJ span{fill:#333;color:#333;}#mermaid-svg-Epg8muNqTerClcEJ .node rect,#mermaid-svg-Epg8muNqTerClcEJ .node circle,#mermaid-svg-Epg8muNqTerClcEJ .node ellipse,#mermaid-svg-Epg8muNqTerClcEJ .node polygon,#mermaid-svg-Epg8muNqTerClcEJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Epg8muNqTerClcEJ .rough-node .label text,#mermaid-svg-Epg8muNqTerClcEJ .node .label text,#mermaid-svg-Epg8muNqTerClcEJ .image-shape .label,#mermaid-svg-Epg8muNqTerClcEJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-Epg8muNqTerClcEJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Epg8muNqTerClcEJ .rough-node .label,#mermaid-svg-Epg8muNqTerClcEJ .node .label,#mermaid-svg-Epg8muNqTerClcEJ .image-shape .label,#mermaid-svg-Epg8muNqTerClcEJ .icon-shape .label{text-align:center;}#mermaid-svg-Epg8muNqTerClcEJ .node.clickable{cursor:pointer;}#mermaid-svg-Epg8muNqTerClcEJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Epg8muNqTerClcEJ .arrowheadPath{fill:#333333;}#mermaid-svg-Epg8muNqTerClcEJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Epg8muNqTerClcEJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Epg8muNqTerClcEJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Epg8muNqTerClcEJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Epg8muNqTerClcEJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Epg8muNqTerClcEJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Epg8muNqTerClcEJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Epg8muNqTerClcEJ .cluster text{fill:#333;}#mermaid-svg-Epg8muNqTerClcEJ .cluster span{color:#333;}#mermaid-svg-Epg8muNqTerClcEJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Epg8muNqTerClcEJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Epg8muNqTerClcEJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-Epg8muNqTerClcEJ .icon-shape,#mermaid-svg-Epg8muNqTerClcEJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Epg8muNqTerClcEJ .icon-shape p,#mermaid-svg-Epg8muNqTerClcEJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Epg8muNqTerClcEJ .icon-shape .label rect,#mermaid-svg-Epg8muNqTerClcEJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Epg8muNqTerClcEJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Epg8muNqTerClcEJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Epg8muNqTerClcEJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 触发 AOF 重写
fork 子进程
子进程:遍历数据库

生成新的 AOF 文件

(写入临时文件)
重写期间

主进程的写命令

同时写入 aof_buf + aof_rewrite_buf
子进程完成重写
主进程:将 aof_rewrite_buf

追加到新 AOF 文件
原子 rename 替换旧文件

触发条件(redis.conf):

bash 复制代码
auto-aof-rewrite-percentage 100   # 当前 AOF 文件是上次重写后大小的 2 倍
auto-aof-rewrite-min-size 64mb    # 且 AOF 文件大于 64MB

四、混合持久化(Redis 4.0+):RDB + AOF 的王炸组合

4.1 解决了什么问题?

RD B 和 AOF 各有短板:

  • RDB 恢复快但可能丢数据多 → AOF 恢复慢但丢数据少
  • AOF 恢复慢但丢数据少 → RDB 恢复快但可能丢数据多

有没有一种方案,能用 RDB 的速度做恢复,用 AOF 的粒度保数据?这就是混合持久化。

4.2 混合持久化的文件结构

bash 复制代码
# redis.conf
aof-use-rdb-preamble yes   # Redis 5.0+ 默认开启

混合持久化的 AOF 文件结构:

复制代码
[ RDB 二进制快照部分 ] + [ AOF 文本命令部分 ]
   ↑ 重写时的全量数据       ↑ 重写后的增量命令

文件内容示意:
REDIS0009ú...(RDB 格式)...þ(EOF 标记)
*3\r\n$3\r\nSET\r\n...       (后续增量 AOF 命令)
*3\r\n$4\r\nINCR\r\n...

重写时:子进程以 RDB 格式把当前全量数据写入 AOF 文件开头。

重写后:主进程的所有新写命令以 AOF 格式追加在文件末尾。

恢复时:先按 RDB 协议加载前半部分的快照数据,再按 AOF 协议回放后半部分的增量命令。

4.3 这是目前的最佳实践

混合持久化 = RDB 的恢复速度 + AOF 的数据安全性 ,是 Redis 4.0 以来最重要的持久化改进。2024 年的生产环境,aof-use-rdb-preamble yes 应该成为标配。


五、Redis 7.0 Multi-Part AOF:持久化架构的一次重构

5.1 老版本 AOF 的痛点

在 Redis 7.0 之前,AOF 管理有几个让人头疼的问题:

  1. 重写过程中的增量缓冲 :旧版本用一个内存缓冲区 aof_rewrite_buf 存重写期间的增量命令。如果写入量大,这个缓冲区可能撑爆内存。

  2. 单一 AOF 文件:只有一个 AOF 文件,重写时要创建临时文件然后 rename。如果中途失败,旧文件状态不确定。

  3. AOF 时间戳没有原生支持:做增量同步或数据恢复时不够灵活。

5.2 Multi-Part AOF 架构

Redis 7.0 将 AOF 文件拆分为多个部分,每个部分有明确的职责:
#mermaid-svg-2fTNo7gXH7Ah8AXL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2fTNo7gXH7Ah8AXL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2fTNo7gXH7Ah8AXL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2fTNo7gXH7Ah8AXL .error-icon{fill:#552222;}#mermaid-svg-2fTNo7gXH7Ah8AXL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2fTNo7gXH7Ah8AXL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2fTNo7gXH7Ah8AXL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2fTNo7gXH7Ah8AXL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2fTNo7gXH7Ah8AXL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2fTNo7gXH7Ah8AXL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2fTNo7gXH7Ah8AXL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2fTNo7gXH7Ah8AXL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2fTNo7gXH7Ah8AXL .marker.cross{stroke:#333333;}#mermaid-svg-2fTNo7gXH7Ah8AXL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2fTNo7gXH7Ah8AXL p{margin:0;}#mermaid-svg-2fTNo7gXH7Ah8AXL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2fTNo7gXH7Ah8AXL .cluster-label text{fill:#333;}#mermaid-svg-2fTNo7gXH7Ah8AXL .cluster-label span{color:#333;}#mermaid-svg-2fTNo7gXH7Ah8AXL .cluster-label span p{background-color:transparent;}#mermaid-svg-2fTNo7gXH7Ah8AXL .label text,#mermaid-svg-2fTNo7gXH7Ah8AXL span{fill:#333;color:#333;}#mermaid-svg-2fTNo7gXH7Ah8AXL .node rect,#mermaid-svg-2fTNo7gXH7Ah8AXL .node circle,#mermaid-svg-2fTNo7gXH7Ah8AXL .node ellipse,#mermaid-svg-2fTNo7gXH7Ah8AXL .node polygon,#mermaid-svg-2fTNo7gXH7Ah8AXL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2fTNo7gXH7Ah8AXL .rough-node .label text,#mermaid-svg-2fTNo7gXH7Ah8AXL .node .label text,#mermaid-svg-2fTNo7gXH7Ah8AXL .image-shape .label,#mermaid-svg-2fTNo7gXH7Ah8AXL .icon-shape .label{text-anchor:middle;}#mermaid-svg-2fTNo7gXH7Ah8AXL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2fTNo7gXH7Ah8AXL .rough-node .label,#mermaid-svg-2fTNo7gXH7Ah8AXL .node .label,#mermaid-svg-2fTNo7gXH7Ah8AXL .image-shape .label,#mermaid-svg-2fTNo7gXH7Ah8AXL .icon-shape .label{text-align:center;}#mermaid-svg-2fTNo7gXH7Ah8AXL .node.clickable{cursor:pointer;}#mermaid-svg-2fTNo7gXH7Ah8AXL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2fTNo7gXH7Ah8AXL .arrowheadPath{fill:#333333;}#mermaid-svg-2fTNo7gXH7Ah8AXL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2fTNo7gXH7Ah8AXL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2fTNo7gXH7Ah8AXL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2fTNo7gXH7Ah8AXL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2fTNo7gXH7Ah8AXL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2fTNo7gXH7Ah8AXL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2fTNo7gXH7Ah8AXL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2fTNo7gXH7Ah8AXL .cluster text{fill:#333;}#mermaid-svg-2fTNo7gXH7Ah8AXL .cluster span{color:#333;}#mermaid-svg-2fTNo7gXH7Ah8AXL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2fTNo7gXH7Ah8AXL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2fTNo7gXH7Ah8AXL rect.text{fill:none;stroke-width:0;}#mermaid-svg-2fTNo7gXH7Ah8AXL .icon-shape,#mermaid-svg-2fTNo7gXH7Ah8AXL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2fTNo7gXH7Ah8AXL .icon-shape p,#mermaid-svg-2fTNo7gXH7Ah8AXL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2fTNo7gXH7Ah8AXL .icon-shape .label rect,#mermaid-svg-2fTNo7gXH7Ah8AXL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2fTNo7gXH7Ah8AXL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2fTNo7gXH7Ah8AXL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2fTNo7gXH7Ah8AXL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} History AOF(历史文件)
Incremental AOF(增量文件)
Base AOF(基础文件)
重写后
重写后
AOF Manifest(清单文件)
appendonly.aof.manifest

记录所有 AOF 文件列表
appendonly.aof.1.base.rdb

RDB 格式的全量快照
appendonly.aof.1.incr.aof

增量 AOF 命令
appendonly.aof.2.incr.aof

下一个增量文件
appendonly.aof.1.base.rdb

被新 base 替代的旧快照
appendonly.aof.1.incr.aof

已被合并的旧增量

文件类型说明

文件类型 后缀 内容 创建时机
Base .base.rdb RDB 格式的全量快照 AOF 重写时创建
Incremental .incr.aof 增量 AOF 命令 达到大小阈值时轮转
Manifest .manifest 清单(记录所有文件的顺序和类型) 始终存在

5.3 Manifest 文件示例

复制代码
# appendonly.aof.manifest
file appendonly.aof.1.base.rdb seq 1 type b
file appendonly.aof.1.incr.aof seq 1 type i
file appendonly.aof.2.incr.aof seq 2 type i
  • seq:序列号,恢复时按序加载
  • type b:base 文件(RDB 格式)
  • type i:incremental 文件(AOF 格式)

5.4 Multi-Part AOF 的优势

问题 旧版本 Redis 7.0 Multi-Part AOF
重写时增量缓冲过大 单一大内存缓冲区 写到新的 incr 文件,无内存压力
重写失败 临时文件状态不清 新文件独立,旧文件仍可用
文件过大 手动 BGREWRITEAOF 自动轮转(incr 文件达到阈值自动切新文件)
恢复灵活性 全有或全无 可以按 manifest 选择性加载
磁盘空间 重写后才释放旧文件 渐进式清理历史文件

六、生产环境持久化方案选型

理论知识讲完了,下面是实实在在生产环境的配置方案。

6.1 方案对比

方案 配置 数据安全性 恢复速度 性能影响 适用场景
纯缓存 RDB 关闭 + AOF 关闭 - 最优 纯缓存,数据可从数据库重建
仅 RDB save 900 1 300 10 60 10000 可能丢几分钟 缓存为主,可接受少量数据丢失
仅 AOF appendonly yes + appendfsync everysec 高(最多 1 秒) 数据重要,可接受稍慢的恢复
RDB + AOF 双开 同时开启 RDB 和 AOF 较快(优先用 AOF 恢复) 推荐方案
混合持久化 双开 + aof-use-rdb-preamble yes 生产最佳实践

6.2 推荐的生产配置

bash 复制代码
# ============ RDB 配置 ============
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes   # BGSAVE 失败时拒绝写入(防止数据持续丢失而不自知)
rdbcompression yes                # 开启 LZF 压缩
rdbchecksum yes                   # CRC64 校验

# ============ AOF 配置 ============
appendonly yes                    # 开启 AOF
appendfsync everysec              # 每秒 fsync,性能与安全的平衡点
no-appendfsync-on-rewrite yes     # 重写期间不 fsync(避免 IO 争抢,轻微降低安全性提升性能)
auto-aof-rewrite-percentage 100   # AOF 文件增长 100% 触发重写
auto-aof-rewrite-min-size 64mb    # AOF 文件至少 64MB 才触发重写
aof-use-rdb-preamble yes          # 混合持久化(Redis 7.0+ 默认)

# ============ Multi-Part AOF(Redis 7.0+)============
aof-timestamp-enabled no          # 是否在 AOF 中记录时间戳(用于增量恢复,一般不需要)

6.3 常见坑点与避坑指南

坑 1:AOF 文件损坏

bash 复制代码
# 症状:重启时 Redis 报错 "Bad file format reading the append only file"
# 修复:
redis-check-aof --fix appendonly.aof
# 原理:截断到最后一个完整命令,丢弃尾部损坏部分

坑 2:AOF 文件过大导致恢复极慢

bash 复制代码
# 检查 AOF 大小
ls -lh appendonly.aof

# 如果几十 GB,手动触发重写
redis-cli BGREWRITEAOF

# 预防:降低 auto-aof-rewrite-min-size,提高重写频率

坑 3:BGSAVE 期间内存暴涨 OOM

bash 复制代码
# 本质是 COW 导致的内存翻倍
# 解决:
# 1. 预留 50% 以上空闲内存
# 2. 调整 save 触发条件,避免写入高峰触发
# 3. 使用 info memory 监控 used_memory_rss / used_memory 比值

坑 4:写入性能突然下降

bash 复制代码
# 如果配置了 appendfsync always,磁盘 IO 直接成为瓶颈
# 改成 everysec 即可解决大部分问题

# 如果不是 always 模式,检查是否正在 AOF 重写
info persistence | grep aof_rewrite_in_progress
# 设置 no-appendfsync-on-rewrite yes 可缓解

6.4 监控指标

生产环境必需监控的持久化相关指标:

bash 复制代码
# 关键指标
INFO Persistence

# RDB
rdb_last_bgsave_status        # ok / err,上次 BGSAVE 是否成功
rdb_last_bgsave_time_sec      # 上次 BGSAVE 耗时(秒),突然变长要警惕
rdb_changes_since_last_save   # 距离上次保存的变更数,越大越危险

# AOF
aof_enabled                   # 是否开启
aof_current_size              # 当前 AOF 文件大小
aof_last_rewrite_time_sec     # 上次重写耗时
aof_rewrite_in_progress       # 是否正在重写
aof_last_bgrewrite_status     # 上次重写状态
aof_buffer_length             # AOF 缓冲区大小(过大说明写入积压)

# fork 耗时(重点监控)
latest_fork_usec              # 最近一次 fork 的耗时(微秒)
# 如果这个值超过 100ms,说明系统内存压力大,需要关注

七、总结

本文从三个层面讲透了 Redis 持久化:

层面 核心内容
原理层 RDB 的 fork+COW 机制、AOF 的三阶段写入和 fsync 策略、混合持久化的文件结构
演进层 Redis 7.0 Multi-Part AOF 如何解决旧版本缓冲区膨胀和文件管理的痛点
实践层 多种方案的配置对比、5 个常见坑点及解决方案、生产监控指标

一句话总结持久化选型 :绝大多数场景直接上 RDB + AOF + 混合持久化appendfsynceverysecaof-use-rdb-preambleyes。这是经过千万级实例验证的最佳配置。


如有疑问或指正,欢迎在评论区交流。

相关推荐
We Just Keep growing1 小时前
【MySQL运维篇】——日志、主从复制、分库分表、读写分离
java·运维·数据库·windows·学习·mysql
橙子圆1231 小时前
Redis知识10之缓存
数据库·redis·缓存
每天都要进步哦1 小时前
MySQL快速入门指南:从零基础到基本操作
数据库·mysql·oracle
bjzhang751 小时前
mysql 常用命令
数据库·mysql
计算机安禾2 小时前
【算法分析与设计】第48篇:流算法与数据概要技术
java·服务器·网络·数据库·算法
情绪总是阴雨天~2 小时前
基于 Docker 的 Milvus + Redis 本地开发环境部署完全指南
redis·docker·milvus
数据库小学妹2 小时前
时序数据库核心原理拆解:写入吞吐、压缩存储、融合分析全链路分析
数据库·经验分享·时序数据库·dba
我是一颗柠檬2 小时前
【Redis】Redis缓存应用实战Day12(2026年)
数据库·redis·缓存
zzz_23682 小时前
【Redis】Redis 面试深度系列
数据库·redis·面试