Skip to content
持久化

持久化

Redis 是一个内存数据库,所有数据默认存储在内存中。一旦进程退出或服务器断电,所有数据将不可逆转地丢失。持久化(Persistence)就是将内存中的数据写入磁盘,以便在重启后恢复。


1. 持久化概述

1.1 为什么需要持久化

内存(RAM)是易失性存储——断电即丢。Redis 的高性能正来源于纯内存操作,但这也带来一个根本问题:数据安全。持久化就是在"高性能"和"数据安全"之间寻找平衡点的机制。

1.2 三种持久化方案

Redis 提供了三种持久化方案,可以单独使用或组合使用:

方案 原理 引入版本 数据丢失窗口
RDB (Redis Database Backup) 内存快照,某一时刻的全量数据拷贝 Redis 1.0 两次快照之间的所有写入
AOF (Append Only File) 记录每条写命令到日志,重启时重放 Redis 1.0 取决于 appendfsync 策略
混合持久化 (RDB + AOF) AOF 文件前半部分为 RDB 快照,后半部分为增量 AOF 命令 Redis 4.0 取决于 appendfsync 策略

此外,Redis 还支持 No Persistence 模式——完全关闭持久化,数据仅在内存中存在,适合纯缓存场景。

1.3 各版本重大变更

版本 变更
Redis 1.0 RDB 和 AOF 基础支持
Redis 2.8 自动 RDB 恢复检测、AOF 重写改进
Redis 4.0 混合持久化(aof-use-rdb-preamble)
Redis 7.0 Multi-Part AOF(appenddirname 目录结构)

2. RDB (Redis Database Backup)

2.1 原理

RDB 持久化在某个时间点对 Redis 中的所有数据生成一个二进制压缩快照(snapshot),保存到磁盘。它不是一条一条记录写命令,而是直接保存当前内存中所有键值对的完整状态。

🔬 深入原理:Fork 子进程 + Copy-On-Write (COW)

RDB 不能由主进程直接执行(否则会长时间阻塞所有请求),因此 Redis 采用 fork 子进程的方案:

┌──────────────────┐     fork()      ┌──────────────────┐
│   Redis 主进程    │ ──────────────► │   子进程           │
│  (继续处理请求)   │                │  (写入RDB文件)     │
│                  │                │                  │
│  共享物理内存页   │ ◄──── COW ───► │  共享物理内存页    │
└──────────────────┘                └──────────────────┘

COW 机制详解:

  1. fork() 调用:父进程调用 fork() 创建子进程。OS 内核会为子进程创建页表(Page Table)副本,但父子进程共享同一份物理内存页。此时并没有发生实际的内存复制,仅复制了页表条目(约占用父进程内存的 0.1%-0.2%)。
  2. 页表标记为只读:所有共享的内存页被标记为 Copy-On-Write。
  3. 写入时复制:当父进程(Redis 主进程)收到写请求,需要修改某个 key 对应的内存页时,OS 会先复制该页的原始内容给子进程使用,然后让父进程修改新的副本。只有被修改的页才会被复制,未修改的页继续共享。
  4. 子进程只读:子进程只遍历内存数据并写入磁盘,不会修改任何内存,因此子进程一侧永远不会触发 COW。

COW 对内存的消耗估算:

额外内存 ≈ fork 时内存 × 写入比例 × 页表开销
  • 页表开销:约 0.1%-0.2% 的父进程内存(例如 10GB 实例约需 10-20MB 页表)。
  • 写入比例:取决于 RDB 生成期间(通常数分钟至数十分钟)有多少内存页被修改。
  • 保守建议:预留 30%-50% 的内存余量以应对极端写入场景。

🚨 陷阱:如果 Redis 在写密集场景下 fork,且剩余可用内存不足,可能触发 OOM Killer 被系统杀掉。

2.2 触发方式

RDB 快照有四种触发方式:

2.2.1 SAVE —— 同步阻塞

redis-cli> SAVE
OK
  • 执行者:Redis 主进程
  • 行为:在主进程中直接执行 RDB 写入,期间阻塞所有客户端请求
  • 耗时:取决于数据量,大数据集可能需要数秒到数分钟
  • 适用场景:仅适合在关闭节点前做最后一次保存,或低峰期手动备份

🚨 陷阱:SAVE 在生产环境几乎不应使用——它会阻塞整个实例,导致所有客户端超时。除非你明确知道自己在做什么,否则永远用 BGSAVE。

2.2.2 BGSAVE —— 异步非阻塞

redis-cli> BGSAVE
Background saving started

执行流程:

客户端请求 BGSAVE
    │
    ▼
主进程 fork() 创建子进程
    │
    ├── 父进程:继续处理客户端请求
    │
    └── 子进程:遍历内存数据 → 写入临时文件 → rename() 原子替换 dump.rdb
  • fork 阶段:主进程会短暂阻塞(时间取决于内存大小,10GB 约需 50-200ms)
  • 写入阶段:子进程执行,主进程不阻塞
  • 原子替换:子进程先写入临时文件(temp-<pid>.rdb),完成后通过 rename(2) 系统调用原子替换旧的 dump.rdb
  • 数据一致性:由于 COW,RDB 文件代表 fork 那一瞬间的数据状态

INFO 命令查看状态:

redis-cli> INFO persistence
# Persistence
loading:0                     # 是否正在加载
rdb_bgsave_in_progress:0      # 是否正在 BGSAVE
rdb_last_bgsave_status:ok     # 上次 BGSAVE 结果
rdb_last_bgsave_time_sec:3    # 上次 BGSAVE 耗时(秒)
rdb_current_bgsave_time_sec:-1
rdb_last_cow_size:0

2.2.3 自动触发 —— save 配置

在 redis.conf 中配置条件自动触发 BGSAVE:

save 900 1     # 900 秒(15 分钟)内至少 1 个 key 被修改
save 300 10    # 300 秒(5 分钟)内至少 10 个 key 被修改
save 60 10000  # 60 秒内至少 10000 个 key 被修改
  • 多条 save 配置是或关系,任意一条满足即触发 BGSAVE。
  • Redis 内部维护一个 dirty 计数器(记录上次保存后的修改次数)和一个 lastsave 时间戳。
  • serverCron 函数每 100ms 检查一次是否满足条件。
  • 配置 save "" 可以禁用自动 RDB。
save ""   # 禁用自动 RDB 保存

2.2.4 其他触发场景

触发场景 执行命令 说明
SHUTDOWN 时 SHUTDOWN SAVE 关闭前自动执行一次 BGSAVE(若 AOF 未启用)
主从全量同步 主节点自动 BGSAVE Master 为每个 Slave 执行 BGSAVE 生成 RDB
DEBUG RELOAD 内部调用 调试用,重新加载 RDB
FLUSHALL 后 不自动触发 手动执行 BGSAVE 否则 RDB 为空 ⚠️

2.3 配置参数详解

# /etc/redis/redis.conf 或 redis.conf

# ── RDB 触发条件 ──
save 900 1                              # 15 分钟内至少 1 次修改
save 300 10                             # 5 分钟内至少 10 次修改
save 60 10000                           # 60 秒内至少 10000 次修改
save ""                                 # 设为空字符串禁用自动 RDB

# ── 文件名与路径 ──
dbfilename dump.rdb                     # RDB 文件名
dir ./                                  # RDB 文件存储目录(同时影响 AOF)

# ── 压缩与校验 ──
rdbcompression yes                      # 是否使用 LZF 算法压缩字符串
                                        #   yes: 节省磁盘 ~60%,但增加 CPU 开销
                                        #   no:  文件更大,但 CPU 开销更低

rdbchecksum yes                         # 是否在 RDB 文件末尾写入 CRC64 校验和
                                        #   用于检测文件是否损坏,写入和读取时均有开销

# ── 错误处理 ──
stop-writes-on-bgsave-error yes         # BGSAVE 失败时是否拒绝写入
                                        #   yes: 阻止写入,防止进程悄悄丢失持久化
                                        #   no:  继续接受写入(风险:数据无法持久化)

各参数深入说明:

参数 默认值 建议 说明
save 三条规则 按写入量调整 写入量小可适当放宽,写入量大适当收紧
dbfilename dump.rdb 保持默认 备份时建议加时间戳:dump_$(date +%Y%m%d).rdb
dir ./ 设为独立目录 确保目录有足够空间和写入权限
rdbcompression yes yes CPU 不是瓶颈时开启,节省大量磁盘空间
rdbchecksum yes yes 生产环境建议开启,能检测文件损坏
stop-writes-on-bgsave-error yes yes 生产环境强烈建议开启

2.4 RDB 优缺点

优点 缺点
文件紧凑,适合备份和灾难恢复 两次 RDB 之间的数据可能丢失
恢复大数据集比 AOF 快(二进制直接加载) fork 子进程耗时,内存越大越慢
对主进程性能影响小(子进程异步写入) fork 时内存翻倍风险(COW + 页表)
适合冷备、离线分析、数据迁移 频繁 fork 拷贝页表有 CPU 开销
RDB 文件可用于主从同步 不是实时持久化,不适合高可靠性需求

⚡ 性能提示:BGSAVE 的 fork 阶段耗时主要由内存大小决定(而非 QPS 或键数量)。10GB 内存的实例,fork 约需要 50-200ms。如果对延迟极度敏感,应控制单实例内存 < 10GB,或使用 Redis Cluster 分片。

2.5 🔬 RDB 文件结构

RDB 文件是一个二进制格式文件,结构如下:

┌──────────────────────────────────────────────────┐
│  Header        │ REDIS0009           (9 bytes)    │
├──────────────────────────────────────────────────┤
│  Aux Fields    │ redis-ver, redis-bits, ctime,    │
│                │ used-mem 等辅助信息              │
├──────────────────────────────────────────────────┤
│  DB 0          │ SELECT 0 + Key-Value Pairs       │
├──────────────────────────────────────────────────┤
│  DB 1          │ SELECT 1 + Key-Value Pairs       │
├──────────────────────────────────────────────────┤
│  ...           │ ...                              │
├──────────────────────────────────────────────────┤
│  DB N          │ SELECT N + Key-Value Pairs       │
├──────────────────────────────────────────────────┤
│  EOF           │ 0xFF               (1 byte)      │
├──────────────────────────────────────────────────┤
│  Checksum      │ CRC64              (8 bytes)     │
└──────────────────────────────────────────────────┘

各部分详解:

  1. 文件头(Header):固定字符串 REDIS + 4 位版本号(如 0009 表示 RDB 版本 9),共 9 字节。
  2. 辅助字段(Aux Fields):Redis 版本、系统位数(32/64 位)、创建时间戳、内存使用量等元信息。
  3. 数据部分(Databases):按 DB 编号顺序排列,每个 DB 以 SELECTDB 操作码开头,后跟键值对。
  4. 文件尾(EOF):固定字节 0xFF,标识数据部分结束。
  5. 校验和(Checksum):可选的 CRC64 校验和,从文件头到 EOF 的校验值。

每种数据类型的存储格式简述:

Redis 类型 RDB 编码 存储内容
String RDB_TYPE_STRING (0) 直接存储字符串值
List RDB_TYPE_LIST (1) / RDB_TYPE_LIST_QUICKLIST (14) 快速列表(Redis 3.2+ 使用 quicklist)
Set RDB_TYPE_SET (2) / RDB_TYPE_SET_INTSET (11) 小集合用 intset,大集合用 hashtable
ZSet RDB_TYPE_ZSET (3) / RDB_TYPE_ZSET_ZIPLIST (12) 小有序集合用 ziplist,大集合用 skiplist
Hash RDB_TYPE_HASH (4) / RDB_TYPE_HASH_ZIPLIST (13) 小哈希用 ziplist,大哈希用 hashtable
Stream RDB_TYPE_STREAM_LISTPACKS (15) listpack 紧凑编码

3. AOF (Append Only File)

3.1 原理

AOF(Append Only File)持久化记录每一条写入命令到日志文件中,重启时按顺序**重放(replay)**所有命令来重建数据库状态。

与 RDB 的对比:

RDB:  [数据] ──► dump.rdb                      (快照:保存数据本身)
AOF:  [SET k v] ──► appendonly.aof             (日志:保存生成数据的命令)

工作流程:

Client Write Command
    │
    ▼
┌──────────────────────────────────────────┐
│  1. Redis 执行命令,修改内存数据           │
│  2. 将命令以 RESP 协议格式追加到            │
│     AOF 缓冲区 (aof_buf)                  │
│  3. 根据 appendfsync 策略调用 fsync()     │
│     将缓冲区内容写入磁盘                   │
└──────────────────────────────────────────┘

🔬 深入原理:写命令的数据流转

客户端写请求
     │
     ▼
执行命令 (processCommand)
     │
     ├──► 修改内存中的数据 (dbAdd / dbOverwrite 等)
     │
     └──► 如果 AOF 已开启:
          │
          ▼
     将命令写入 aof_buf (sds 缓冲区)
          │
          ▼
     事件循环 (beforeSleep)
          │
          ▼
     flushAppendOnlyFile()
          │
          ├── appendfsync = always  → write + fsync (每条命令)
          ├── appendfsync = everysec → write + 后台线程 fsync (每秒)
          └── appendfsync = no      → write (OS决定何时fsync)

3.2 配置参数详解

# ── AOF 总开关 ──
appendonly yes                          # 开启 AOF 持久化
                                        #   关闭时 (no): 只使用 RDB

# ── 文件名与目录 ──
appendfilename "appendonly.aof"         # AOF 文件名 (7.0+ 为 base file 名称)
appenddirname "appendonlydir"           # 7.0+ Multi-Part AOF 文件目录

# ── fsync 策略 ──
appendfsync everysec                    # 每秒 fsync 一次 (默认推荐)
                                        #   always: 每次写都 fsync
                                        #   no:     不主动 fsync

# ── Rewrite 相关 ──
no-appendfsync-on-rewrite yes           # Rewrite 期间不执行 fsync
                                        #   yes: 由 OS 缓冲处理
                                        #   no:  正常 fsync(可能阻塞)

auto-aof-rewrite-percentage 100         # 当前 AOF 文件相对上次 Rewrite 后增长的百分比
auto-aof-rewrite-min-size 64mb          # AOF 文件最小体积才触发 Rewrite

# ── 加载与恢复 ──
aof-load-truncated yes                  # 遇到尾部截断的 AOF 文件如何处理
                                        #   yes: 加载并警告
                                        #   no:  拒绝启动,需 redis-check-aof 修复

# ── 混合持久化 ──
aof-use-rdb-preamble yes                # 是否在 AOF Rewrite 时使用 RDB 格式做前置内容
                                        #   yes: 混合持久化(推荐 4.0+)
                                        #   no:  纯 AOF 格式

3.3 appendfsync 三种策略对比

AOF 文件写入涉及两个系统调用:write(2) 和 fsync(2)。

  • write(2):将数据从用户态缓冲区写入内核的 page cache。此时数据尚未写入物理磁盘。
  • fsync(2):将内核 page cache 中的数据强制刷入物理磁盘。这才是真正的持久化。
用户态 aof_buf
    │  write(2)
    ▼
内核 page cache (断电丢失)
    │  fsync(2)
    ▼
物理磁盘 (持久化)
策略 write fsync 数据丢失窗口 对性能影响 适用场景
always 每条命令 每条命令 最多 1 条命令 极大(磁盘速度成为瓶颈) 金融、订单等极致可靠性要求
everysec 每条命令 每秒 1 次 最多 1 秒数据 较小(推荐) 高可靠性要求的通用场景
no 每条命令 不调用 OS 决定(通常 30s) 几乎无影响 可容忍大量丢失的场景

💡 最佳实践:everysec 是生产环境的最佳平衡点——性能损失通常 < 5%,数据丢失窗口可控在 1 秒内。always 仅在极端可靠性要求下使用,因为磁盘 IO 会成为瓶颈,单实例 QPS 可能从 10 万降到几千。

🔬 深入原理:everysec 的实现

┌────────────────────────────────────────────────┐
│  主线程                        后台 fsync 线程   │
│                                                  │
│  write() 到 page cache ────────► fsync() 每秒    │
│  (每条命令都 write)                              │
│                                                  │
│  注意:如果后台线程上一次 fsync 尚未完成,        │
│  主线程可以选择:                                │
│  - 延迟 write(默认,最多延迟 2 秒)              │
│  - 直接 write(由 OS 缓冲,有写入丢失风险)       │
└────────────────────────────────────────────────┘

3.4 AOF 重写 (Rewrite)

3.4.1 为什么需要重写

AOF 文件随着运行时间持续膨胀——它记录每一条写命令,但很多命令实际上被后续命令覆盖了:

# 没有 Rewrite 的 AOF 文件
SET counter 1       # 无效
SET counter 2       # 无效
SET counter 3       # 无效
...
SET counter 10000   # 前面 9999 条 SET counter 都是冗余的

不重写的后果:

  • 磁盘空间持续被占用,最终磁盘写满
  • 重启恢复时需要重放大量冗余命令,恢复时间极长
  • AOF 加载耗时可能远超 RDB

3.4.2 重写原理

AOF 重写不是分析旧 AOF 文件,而是直接读取当前内存数据,生成一条或多条能够重建这些数据的最精简命令。

┌───────────────┐    fork()     ┌───────────────────┐
│   Redis 主进程  │ ──────────► │  子进程             │
│               │             │                    │
│  持有最新内存  │             │  读取当前内存数据    │
│  数据状态      │             │  生成精简 AOF 命令   │
│               │             │  写入新 AOF 文件     │
└───────────────┘             └───────────────────┘

重写规则示例:

原始 AOF 中的命令 重写后 说明
RPUSH list a; RPUSH list b; RPUSH list c RPUSH list a b c 多条合并为一条
SET k 1; SET k 2; SET k 3 SET k 3 保留最终值
LPUSH list a; LTRIM list 0 9 生成对应的精简命令 保留内存中的最终状态
已过期的 key 不写入 过期 key 不需要保存
INCR c * 100 SET c 100 用 SET 表达最终值

3.4.3 BGREWRITEAOF 流程

redis-cli> BGREWRITEAOF
Background append only file rewriting started

完整流程:

客户端请求 BGREWRITEAOF
    │
    ▼
主进程 fork() 创建子进程
    │
    ├── 子进程:读取当前内存 → 生成精简命令 → 写入临时文件
    │
    ├── 父进程:继续处理客户端请求
    │          │
    │          ├──► 写入旧的 AOF 缓冲区(正常 AOF 写入)
    │          └──► 写入 AOF 重写缓冲区(收集子进程重写期间的增量命令)
    │
    ▼(子进程重写完成)
父进程将 AOF 重写缓冲区中的增量命令追加到临时文件末尾
    │
    ▼
父进程通过 rename() 原子替换旧的 AOF 文件

🔬 深入原理:重写缓冲区的双写机制

在子进程执行重写期间,主进程仍在接受写请求。这些写命令需要同时写入两个地方:

  1. AOF 缓冲区(aof_buf):正常流程,最终写入旧的 AOF 文件。
  2. AOF 重写缓冲区(aof_rewrite_buf):收集重写期间的增量命令。

子进程完成重写后,父进程将重写缓冲区内容追加到临时 AOF 文件末尾,然后原子替换。这样确保重写后的 AOF 文件包含 fork 时刻到重写完成之间的所有变更。

3.4.4 自动触发条件

两个参数必须同时满足才会自动触发 BGREWRITEAOF:

auto-aof-rewrite-percentage 100   # 当前 AOF 文件大小 / 上次 Rewrite 后大小 > 100%
                                   # 即当前大小是上次的 2 倍以上

auto-aof-rewrite-min-size 64mb    # AOF 文件必须大于 64MB 才触发

计算逻辑(Redis 源码 serverCron 中):

// 伪代码
long long growth = current_aof_size - last_rewrite_size;
long long percentage = (growth * 100) / last_rewrite_size;

if (current_aof_size >= auto_aof_rewrite_min_size &&
    percentage >= auto_aof_rewrite_percentage) {
    // 触发 BGREWRITEAOF
}

示例:

    时间线
    ─────►
    Rewrite 后: AOF = 64MB  (last_rewrite_size = 64MB)
    写入了 64MB: AOF = 128MB → growth = 100% → 满足触发条件
    写入了 32MB: AOF = 160MB → growth = 150% → 再次满足

3.5 🔬 Multi-Part AOF (Redis 7.0+)

Redis 7.0 重构了 AOF 持久化机制,引入了 Multi-Part AOF,将单文件 AOF 拆分为多文件管理。

3.5.1 旧版 AOF 的问题(7.0 之前)

appendonly.aof                    (单文件)
    ├── 正常写入的命令
    ├── Rewrite 后的精简命令
    └── Rewrite 期间的增量命令

问题:单一文件管理不灵活,Rewrite 时必须整个文件替换

3.5.2 Multi-Part AOF 目录结构

appendonlydir/                    (AOF 目录)
    ├── appendonly.aof.1.base.rdb   (Base file: RDB 格式快照)
    ├── appendonly.aof.1.incr.aof   (Incr file: 增量 AOF 命令)
    ├── appendonly.aof.2.incr.aof   (下一个增量文件)
    └── appendonly.aof.manifest     (清单文件: 元数据)

各部分说明:

文件 作用 格式
*.base.rdb 某个时间点的数据快照(类似 RDB) RDB 二进制
*.incr.aof Base 之后的增量写命令 AOF 文本命令
manifest 记录 Base 和 Incr 文件的顺序及元信息 键值对文本

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), i = incr (增量 AOF)

3.5.3 Multi-Part AOF 的优势

优势 说明
更灵活的管理 可以单独管理、清理增量文件
减少 IO 压力 Rewrite 时只需创建新的 Base + 新的 Incr,旧 Incr 可回收
更好的容错 单个增量文件损坏不影响 Base 部分的恢复
平滑的重写 不需要一次性替换整个大文件

⚡ 性能提示:Multi-Part AOF 在 Redis 7.0+ 默认启用(只要开启了 AOF),无需额外配置。升级到 7.0 后旧的单文件 AOF 会自动迁移。

3.6 AOF 修复

当 AOF 文件损坏(如 Redis 崩溃导致文件尾部不完整),Redis 提供了修复工具。

3.6.1 检查 AOF 文件

redis-check-aof appendonly.aof

输出示例:

AOF analyzed: size=185433943, ok_up_to=185433944, diff=1
AOF is not valid. Use the --fix option.
  • ok_up_to:最后一个有效字节的偏移量
  • diff:之后无效的字节数

3.6.2 修复 AOF 文件

redis-check-aof --fix appendonly.aof

修复原理:

有效命令1 | 有效命令2 | ... | 不完整命令(损坏)
                            │
                    redis-check-aof --fix
                            │
                            ▼
有效命令1 | 有效命令2 | ...   ← 截断损坏部分

修复工具会:

  1. 逐条解析 AOF 命令直到遇到第一个不合法的命令
  2. 截断该命令及其之后的所有内容
  3. 写入修复后的文件

注意事项:

  • 修复是丢弃策略——损坏命令对应的数据不可恢复
  • 建议先备份原 AOF 文件再执行修复:
    cp appendonly.aof appendonly.aof.bak
    redis-check-aof --fix appendonly.aof
  • 配合 aof-load-truncated yes 可以让 Redis 自动处理轻微截断

4. 混合持久化 (RDB + AOF)

4.1 原理

Redis 4.0 引入了混合持久化,结合了 RDB 的快速恢复和 AOF 的低数据丢失。

纯 AOF 文件:
    SET k v | INCR c | SET k2 v2 | ...     ← 全是 AOF 命令
    (恢复时需逐条重放,速度慢)

混合持久化文件:
    ┌──────────────────────┬──────────────────┐
    │  RDB 格式快照         │  AOF 增量命令     │
    │  (二进制、压缩)       │  (RESP 协议文本)  │
    └──────────────────────┴──────────────────┘
    ← 加载时直接读入内存 →  ← 重放这部分命令  →

配置:

aof-use-rdb-preamble yes    # 开启混合持久化 (Redis 4.0+ 默认 yes)

4.2 工作流程

  1. 正常写入:数据通过 AOF 机制写入(appendfsync everysec 控制落盘频率)。
  2. AOF Rewrite 时:子进程将当前内存数据以 RDB 二进制格式写入 AOF 文件开头,然后追加 Rewrite 期间的增量 AOF 命令。
  3. 重启恢复:
    • Redis 检测到 AOF 文件前半部分是 RDB 格式
    • 直接用 RDB 方式加载前半部分(速度快)
    • 再重放后半部分的增量 AOF 命令

4.3 优势总结

维度 纯 RDB 纯 AOF 混合持久化
恢复速度 快 慢(百万命令级极慢) 快(RDB 部分快 + 少量 AOF 重放)
数据丢失 多(快照间隔内) 少(1 秒内) 少(1 秒内)
文件大小 小 大(膨胀后极巨大) 介于两者之间
Rewrite 后文件 N/A 所有命令以 AOF 格式 RDB 压缩格式 + 增量 AOF
兼容性 全版本 全版本 4.0+(低版本 Redis 无法恢复)

💡 最佳实践:混合持久化 + appendfsync everysec 是生产环境的推荐配置。它平衡了恢复速度(RDB 的优势)和数据安全(AOF 的优势),且 Rewrite 后文件更小。

4.4 数据恢复优先级

当 Redis 启动时,数据恢复的优先级为:

检测 AOF 是否开启?
    │
    ├── YES (appendonly yes)
    │   └── 加载 AOF 文件 (包括混合持久化文件)
    │
    └── NO (appendonly no)
        └── 加载 RDB 文件 (dump.rdb)

总结:AOF 优先级 > RDB。即使同时存在 dump.rdb 和 appendonly.aof,只要 appendonly yes,Redis 就优先加载 AOF。


5. 持久化方案选择

5.1 场景推荐

场景 推荐方案 典型配置
纯缓存,数据可从 DB 重建 不持久化 save "" + appendonly no
一般业务(可接受 1s 丢失) 混合持久化(推荐) appendonly yes + aof-use-rdb-preamble yes + appendfsync everysec
高可靠性(订单、金融) AOF always appendonly yes + appendfsync always + save ""(可选关闭 RDB)
灾备/离线分析 纯 RDB + 定时备份 appendonly no + 定时 crontab BGSAVE
只读备份/主从同步 纯 RDB appendonly no + 合理的 save 间隔
大内存实例 (> 20GB) Redis Cluster + 混合持久化 分片后每个实例 < 10GB,fork 延迟可控

🚨 陷阱:如果数据可以从 MySQL/PostgreSQL 等持久存储重建,关闭持久化可以最大化性能。但需要确保故障恢复流程完善且缓存重建不会压垮后端数据库(缓存雪崩)。

5.2 💡 持久化最佳实践

5.2.1 方案选择原则

是否需要数据持久化?
├── NO  → save "" + appendonly no (纯缓存)
└── YES → 可以容忍秒级丢失?
          ├── NO  → appendfsync always (极致可靠)
          └── YES → 混合持久化 + appendfsync everysec (推荐)

5.2.2 RDB 备份策略

# crontab 示例:每小时执行 BGSAVE 并保留最近 24 份
0 * * * * redis-cli -h 127.0.0.1 -p 6379 BGSAVE
5 * * * * cp /var/lib/redis/dump.rdb /backup/redis/dump_$(date +\%Y\%m\%d\%H).rdb

# 保留最近 24 份,删除 24 小时前的
0 0 * * * find /backup/redis/ -name "dump_*.rdb" -mtime +1 -delete

# 每日备份到远程(scp / rsync / 对象存储)
0 6 * * * rsync -avz /var/lib/redis/dump.rdb backup-server:/backup/redis/

5.2.3 AOF fsync 策略

appendfsync everysec    # 首选,性能损失 < 5%,最多丢 1 秒

5.2.4 内存与 fork 管理

建议 原因
单实例内存 < 10GB fork 延迟通常在 100ms 左右;更大的实例 fork 可能达到秒级
预留 30%-50% 内存 给 COW 操作留出空间
使用 Redis Cluster 分片 大数据量时分片,每个实例内存可控
监控 latest_fork_usec INFO stats 中获取,若 > 1 秒需关注
# 查看最近 fork 耗时
redis-cli> INFO stats | grep latest_fork_usec
latest_fork_usec:15234     # 15ms 内正常,> 1s 需关注

5.2.5 避免 100% CPU 时触发持久化

当 CPU 使用率接近 100% 时,BGSAVE/BGREWRITEAOF 的 fork 操作更容易失败或被内核调度延迟。建议:

  • 部署 Redis 时预留 CPU 资源
  • 避免在 CPU 高峰期执行 BGSAVE(或使用 save 条件控制触发时机)
  • 使用 redis-cli --latency 监控延迟

5.2.6 定期测试恢复

# 在生产环境的测试/备份节点上定期执行恢复演练
redis-check-rdb /var/lib/redis/dump.rdb
redis-check-aof /var/lib/redis/appendonly.aof

# 在隔离环境中启动 Redis 验证数据完整性
redis-server --port 6380 --dbfilename test_restore.rdb &
redis-cli -p 6380 DBSIZE   # 验证数据量是否符合预期
redis-cli -p 6380 SHUTDOWN NOSAVE

6. 备份与恢复

6.1 RDB 文件备份脚本

#!/bin/bash
# redis-rdb-backup.sh: RDB 文件定时备份脚本

REDIS_CLI="redis-cli -h 127.0.0.1 -p 6379"
REDIS_DATA_DIR="/var/lib/redis"
BACKUP_DIR="/backup/redis/rdb"
RETENTION_DAYS=7
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

# 确保备份目录存在
mkdir -p "${BACKUP_DIR}"

# 方案1: 触发 BGSAVE 后备份
echo "$(date): Triggering BGSAVE..."
${REDIS_CLI} BGSAVE

# 等待 BGSAVE 完成
while [ "$(${REDIS_CLI} INFO persistence | grep rdb_bgsave_in_progress | cut -d: -f2)" != "0" ]; do
    sleep 1
done

echo "$(date): BGSAVE completed. Copying file..."
cp "${REDIS_DATA_DIR}/dump.rdb" "${BACKUP_DIR}/dump_${TIMESTAMP}.rdb"

# 方案2: 直接复制当前 RDB(不触发新 BGSAVE,适合频繁备份)
# cp "${REDIS_DATA_DIR}/dump.rdb" "${BACKUP_DIR}/dump_${TIMESTAMP}.rdb"

# 压缩
gzip "${BACKUP_DIR}/dump_${TIMESTAMP}.rdb"

# 校验
redis-check-rdb "${BACKUP_DIR}/dump_${TIMESTAMP}.rdb.gz" 2>&1 | grep -q "Checksum OK"
if [ $? -eq 0 ]; then
    echo "$(date): Backup verified successfully: dump_${TIMESTAMP}.rdb.gz"
else
    echo "$(date): Backup verification FAILED!" >&2
    rm -f "${BACKUP_DIR}/dump_${TIMESTAMP}.rdb.gz"
    exit 1
fi

# 清理过期备份
find "${BACKUP_DIR}" -name "dump_*.rdb.gz" -mtime +${RETENTION_DAYS} -delete
echo "$(date): Cleaned up backups older than ${RETENTION_DAYS} days."
echo "$(date): Backup completed successfully."

6.2 AOF 文件备份策略

AOF 文件备份与 RDB 不同——AOF 文件在不断写入,直接复制可能得到不完整的文件。

#!/bin/bash
# redis-aof-backup.sh

BACKUP_DIR="/backup/redis/aof"
AOF_DIR="/var/lib/redis/appendonlydir"      # Redis 7.0+ 目录结构
AOF_FILE="/var/lib/redis/appendonly.aof"    # Redis < 7.0 单文件
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

mkdir -p "${BACKUP_DIR}"

# Redis 7.0+: 备份 Multi-Part AOF 目录
if [ -d "${AOF_DIR}" ]; then
    # 先触发 BGREWRITEAOF,让 AOF 文件进入"干净"状态
    redis-cli BGREWRITEAOF

    # 等待 Rewrite 完成
    while redis-cli INFO persistence | grep -q "aof_rewrite_in_progress:1"; do
        sleep 1
    done

    # 复制整个目录
    cp -r "${AOF_DIR}" "${BACKUP_DIR}/aof_${TIMESTAMP}"
    tar -czf "${BACKUP_DIR}/aof_${TIMESTAMP}.tar.gz" -C "${BACKUP_DIR}" "aof_${TIMESTAMP}"
    rm -rf "${BACKUP_DIR}/aof_${TIMESTAMP}"
else
    # 传统单文件模式
    redis-cli BGREWRITEAOF
    while redis-cli INFO persistence | grep -q "aof_rewrite_in_progress:1"; do
        sleep 1
    done
    cp "${AOF_FILE}" "${BACKUP_DIR}/appendonly_${TIMESTAMP}.aof"
    gzip "${BACKUP_DIR}/appendonly_${TIMESTAMP}.aof"
fi

echo "AOF backup completed: ${TIMESTAMP}"

6.3 异地备份方案

方案 命令/工具 优点 缺点
rsync rsync -avz /backup/redis/ remote:/backup/ 增量传输、带宽友好 需要配置 SSH 密钥
对象存储 aws s3 cp / coscli cp / ossutil cp 高持久性、多副本 需要网络带宽、潜在费用
NFS 挂载 直接写入 NFS 目录 即时异地 NFS 故障影响 Redis
主从复制 Master-Slave 自动同步 准实时、零侵入 无法恢复误删数据

⚠️ 注意:不要将持久化文件写到 NFS 挂载的目录。NFS 延迟或故障会导致 Redis 阻塞或 AOF 损坏。使用 dir 配置本地目录,备份脚本负责传输到异地。

6.4 恢复演练流程

1. 停服或准备验证节点
    ↓
2. 复制持久化文件到恢复目录
    ↓
3. 启动 Redis 测试实例
    ↓
4. 验证数据完整性和数量
    ↓
5. 记录恢复耗时
#!/bin/bash
# redis-restore-drill.sh: 恢复演练脚本

RESTORE_DIR="/tmp/redis_restore_test"
RESTORE_PORT=16379
BACKUP_FILE="/backup/redis/rdb/dump_latest.rdb.gz"

# 1. 准备恢复目录
mkdir -p "${RESTORE_DIR}"
gunzip -c "${BACKUP_FILE}" > "${RESTORE_DIR}/dump.rdb"

# 2. 验证 RDB 文件完整性
redis-check-rdb "${RESTORE_DIR}/dump.rdb"
if [ $? -ne 0 ]; then
    echo "RDB file corrupted! Aborting."
    exit 1
fi

# 3. 启动测试 Redis 实例
echo "Starting test Redis instance on port ${RESTORE_PORT}..."
redis-server --port ${RESTORE_PORT} \
             --dir "${RESTORE_DIR}" \
             --dbfilename dump.rdb \
             --appendonly no \
             --daemonize yes \
             --logfile "${RESTORE_DIR}/redis-restore.log"

sleep 3

# 4. 验证数据
KEY_COUNT=$(redis-cli -p ${RESTORE_PORT} DBSIZE)
echo "Restored database size: ${KEY_COUNT} keys"

# 抽样验证关键数据
redis-cli -p ${RESTORE_PORT} EXISTS user:10001
redis-cli -p ${RESTORE_PORT} GET config:app_version

# 5. 清理
redis-cli -p ${RESTORE_PORT} SHUTDOWN NOSAVE
rm -rf "${RESTORE_DIR}"

echo "Restore drill completed successfully."

7. 数据恢复流程

7.1 RDB 恢复

# 1. 停止 Redis
redis-cli SHUTDOWN SAVE

# 2. 将备份的 RDB 文件放置到 dir 配置的目录
cp /backup/redis/rdb/dump_20260711_030000.rdb /var/lib/redis/dump.rdb

# 3. 确保权限正确
chown redis:redis /var/lib/redis/dump.rdb
chmod 644 /var/lib/redis/dump.rdb

# 4. 启动 Redis
redis-server /etc/redis/redis.conf

RDB 恢复注意事项:

  • 恢复前确保 Redis 实例已完全停止(不要使用 SIGTERM 强杀)。
  • AOF 必须关闭(appendonly no),否则 Redis 优先加载 AOF 文件。
  • RDB 文件放在 dir 配置项指定的目录下。

7.2 AOF 恢复

# 1. 先验证 AOF 文件完整性
redis-check-aof /backup/redis/aof/appendonly.aof

# 2. 停止 Redis
redis-cli SHUTDOWN NOSAVE

# 3. 放置 AOF 文件
# Redis 7.0+ Multi-Part AOF:
cp -r /backup/redis/aof/appendonlydir /var/lib/redis/appendonlydir

# Redis < 7.0 单文件 AOF:
cp /backup/redis/aof/appendonly.aof /var/lib/redis/appendonly.aof

# 4. 确保 AOF 开启
# redis.conf:
#   appendonly yes
#   appendfilename "appendonly.aof"

# 5. 启动 Redis
redis-server /etc/redis/redis.conf

7.3 混合持久化文件恢复

混合持久化文件的恢复流程与纯 AOF 相同——Redis 会自动检测文件格式并选择最佳加载方式:

加载 AOF 文件
    │
    ├── 检测到 RDB 前缀 (REDIS00xx 魔数)
    │   └── 以 RDB 模式加载前半部分
    │       然后重放后半部分的增量 AOF 命令
    │
    └── 未检测到 RDB 前缀
        └── 以纯 AOF 模式逐条重放

不需要特殊配置,Redis 4.0+ 会自动处理。

7.4 优先级总结

Redis 启动时的数据加载优先级:

1. AOF 开启 (appendonly yes)
    ├── AOF 文件存在且有效 → 加载 AOF
    │   ├── 混合持久化 → RDB 快读 + AOF 重放
    │   └── 纯 AOF → 逐条重放
    └── AOF 文件不存在 → 以空数据库启动

2. AOF 关闭 (appendonly no)
    ├── RDB 文件存在 → 加载 RDB
    └── RDB 文件不存在 → 以空数据库启动

7.5 恢复性能对比

数据规模 RDB 恢复 AOF 恢复 混合持久化恢复
1 万条 ~0.1s ~0.3s ~0.1s
100 万条 ~2s ~30s ~2s
1000 万条 ~20s ~5min ~25s
1 亿条 ~3min ~50min ~3.5min

实际恢复时间取决于 CPU、磁盘 IOPS、数据类型复杂度。表内数值仅供参考。


8. 🚨 常见陷阱

8.1 关闭时数据全部丢失

陷阱:Redis 实例运行期间没有开启任何持久化(save "" + appendonly no),SHUTDOWN 或进程崩溃后所有数据永久丢失。

解决:

  • 如果数据需要保留,至少开启 RDB 或 AOF 之一。
  • 即使是缓存,也要评估缓存重建的成本和下游数据库压力。

8.2 fork 子进程时的内存翻倍风险

陷阱:编写密集场景下,RDB 的 BGSAVE 或 AOF 的 BGREWRITEAOF 会在 fork 子进程后因 COW 导致内存使用激增。如果系统没有预留足够内存,会触发 OOM Killer 杀掉 Redis 进程。

假设 Redis 占用 10GB 内存,fork 后:
- 页表拷贝:约 20-200MB
- COW 写入:如果 fork 期间有 50% 的内存页被修改,需额外 5GB
总内存需求可达 15GB+

解决:

  • 系统内存至少为 Redis maxmemory 的 1.5 倍。
  • 监控 used_memory 和系统可用内存。
  • 在低写入时段触发 BGSAVE(通过 save 参数控制触发时间窗口)。
  • 对大数据量实例使用 Redis Cluster 分片。

8.3 RDB save 配置过于频繁

陷阱:误设置为 save 1 1(每秒只要有一次修改就 BGSAVE),导致 fork 子进程的频率极高,CPU 和内存消耗持续走高。

# ❌ 错误配置
save 1 1          # 每秒有修改就触发,灾难性

# ✅ 合理配置
save 900 1        # 15 分钟一次修改就保存
save 300 10       # 5 分钟 10 次修改
save 60 10000     # 1 分钟 10000 次

解决:根据数据重要性和写入量设置合理的 save 间隔。写入越密集,save 条件可以越宽松(Redis 自动 BGSAVE 频率自然就高)。

8.4 AOF Rewrite 期间的阻塞问题

陷阱:no-appendfsync-on-rewrite no(Rewrite 期间正常 fsync)可能导致 fsync 阻塞。

fsync 是一个昂贵的系统调用(尤其 HDD)
Rewrite 期间,父进程需要写入大量数据到 AOF
如果每次都 fsync,可能造成主线程阻塞,延迟飙升

解决:

no-appendfsync-on-rewrite yes    # Rewrite 期间不主动 fsync

设置为 yes,Rewrite 期间写入数据暂存在 OS page cache 中,Rewrite 完成后再由 OS 刷新。最多丢失约 30 秒数据(OS 刷新间隔),但可以避免阻塞。

8.5 AOF 文件无限增长不触发 Rewrite

陷阱:auto-aof-rewrite-min-size 设置过大(如 10GB),导致 AOF 文件远大于该值但仍未触发自动重写。

# ❌ 错误:min-size 过高
auto-aof-rewrite-min-size 10gb    # 10GB 才触发,可能永远达不到

# ✅ 建议
auto-aof-rewrite-min-size 64mb
auto-aof-rewrite-percentage 100

解决:

  • 设置合理的 auto-aof-rewrite-min-size(建议 64MB-1GB)。
  • 监控 aof_current_size 和 aof_last_rewrite_time 指标。
  • 必要时手动执行 BGREWRITEAOF。
  • 写入量极大时可适当降低 auto-aof-rewrite-percentage(如 50%)。

8.6 只开 RDB 不开 AOF

陷阱:只配置了 RDB(如 save 900 1),意外宕机时丢失了最近 900 秒(15 分钟)内的所有数据。

解决:对数据可靠性有要求的场景,开启 AOF(或混合持久化):

appendonly yes
aof-use-rdb-preamble yes
appendfsync everysec

8.7 混合持久化文件的跨版本兼容性

陷阱:使用 Redis 4.0+ 的混合持久化生成的 AOF 文件,无法被 Redis 3.x 或更低版本加载。

# Redis 3.2 尝试加载混合持久化文件
# 错误: "Bad file format reading the append only file"

解决:

  • 升级到 Redis 4.0+ 再开启混合持久化。
  • 迁移数据时使用 redis-cli --rdb 导出标准 RDB 文件,或使用主从同步。
  • 回滚方案:在低版本 Redis 上使用纯 RDB 恢复,或使用 BGREWRITEAOF 并临时关闭 aof-use-rdb-preamble 生成纯 AOF 文件。

8.8 用 kill -9 强杀 Redis

陷阱:kill -9 强杀进程可能导致:

  • AOF 文件尾部不完整(部分写入的命令)
  • appendfsync everysec 下丢失最后一秒的数据

解决:

# ✅ 正确关闭方式
redis-cli SHUTDOWN SAVE     # 先做 BGSAVE 再关闭
redis-cli SHUTDOWN NOSAVE   # 不做 BGSAVE 直接关闭(如果开启了 AOF)

# ✅ 优雅停止
kill -SIGTERM <pid>         # 等同 SHUTDOWN NOSAVE

8.9 磁盘空间满导致持久化失败

陷阱:dir 所在磁盘分区空间耗尽了,BGSAVE/BGREWRITEAOF 因磁盘空间不足失败,但 Redis 主进程仍在正常运行,数据实际处于"无持久化保护"状态。

解决:

  • 监控磁盘使用率(阈值 < 80%)。
  • 配置 stop-writes-on-bgsave-error yes(默认),确保 BGSAVE 失败后 Redis 拒绝写入,强制运维人员介入,避免数据悄悄丢失。
  • 定期清理旧的备份文件。
  • AOF 模式开启 Rewrite,避免文件无限膨胀。

8.10 主从全量同步时的 BGSave 压力

陷阱:一个 Master 挂多个 Slave 同时做全量同步,或 Slave 反复断连触发全量同步,Master 频繁 fork 执行 BGSAVE。

解决:

  • 控制 Slave 数量(建议不超过 3-5 个)。
  • 确保网络稳定,避免 Slave 反复断连。
  • 使用 repl-backlog-size 设置合理大小(建议 64MB-256MB),让 Slave 短时间内重连时可以部分同步而不触发全量同步。
  • Redis 2.8+ 支持无磁盘复制(repl-diskless-sync),直接通过网络传输数据而不写磁盘 RDB。

快速参考卡片

┌─────────────────────────────────────────────────────────────┐
│                    持久化方案速查                              │
├──────────┬──────────────────┬───────────────────────────────┤
│ 纯缓存    │ save ""           │ appendonly no                 │
│ 推荐方案  │ 默认配置           │ 混合持久化 + everysec         │
│ 高可靠    │ appendfsync always│ 丢失 <= 1 条命令              │
│ 灾备      │ 定时 BGSAVE       │ 保留多份 RDB                  │
├──────────┴──────────────────┴───────────────────────────────┤
│ 关键命令                                                      │
│   BGSAVE              — 异步生成 RDB                          │
│   BGREWRITEAOF        — 异步重写 AOF                          │
│   redis-check-rdb     — 验证 RDB 文件                         │
│   redis-check-aof     — 验证/修复 AOF 文件                    │
├─────────────────────────────────────────────────────────────┤
│ 关键指标 (INFO persistence)                                   │
│   rdb_last_bgsave_time_sec   — 上次 BGSAVE 耗时               │
│   aof_current_size            — 当前 AOF 文件大小              │
│   aof_last_rewrite_time_sec   — 上次 Rewrite 耗时             │
│ 关键指标 (INFO stats)                                         │
│   latest_fork_usec            — 最近 fork 耗时(微秒)         │
└─────────────────────────────────────────────────────────────┘