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:序号,决定加载顺序
  • typeb = 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.rdbappendonly.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_sizeaof_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 耗时(微秒)         │
└─────────────────────────────────────────────────────────────┘