Redis 持久化机制详解

49 阅读13分钟

Redis 持久化机制详解:RDB vs AOF vs 混合持久化

Redis 是内存数据库,数据存储在内存中,一旦服务器宕机,内存中的数据就会丢失。为了保证数据安全,Redis 提供了 RDB 快照AOF 日志两种持久化方式,Redis 4.0 后还引入了 混合持久化。本文将深入剖析三种持久化机制的原理、优缺点和最佳实践。

📖 目录


为什么需要持久化?

Redis 的困境

┌─────────────────────────────────┐
│   Redis Server (进程)            │
│                                  │
│   ┌─────────────────────┐       │
│   │   内存数据 (RAM)     │       │
│   │  • user:1001         │       │
│   │  • user:1002         │       │
│   │  • ...               │       │
│   └─────────────────────┘       │
│           ↓                      │
│    💥 服务器宕机/重启             │
│           ↓                      │
│    ❌ 数据全部丢失                │
└─────────────────────────────────┘

持久化的价值

  • 数据安全:防止数据丢失
  • 灾难恢复:服务器宕机后快速恢复
  • 数据迁移:方便数据备份和迁移
  • 主从复制:RDB 文件用于全量同步

持久化机制概览

Redis 提供了三种持久化方式:

持久化方式持久化内容文件格式恢复速度数据完整性适用场景
RDB内存快照二进制可能丢失全量备份、主从同步
AOF操作日志文本更完整数据安全要求高
混合RDB+AOF二进制+文本较快完整兼顾性能和安全

工作流程图

┌──────────────────────────────────────────────┐
│         Redis Server (运行中)                 │
├──────────────────────────────────────────────┤
│  客户端写命令 → 执行命令 → 修改内存数据        │
│                    ↓                          │
│              ┌─────┴─────┐                   │
│              ↓           ↓                    │
│         ┌────────┐  ┌────────┐              │
│         │  RDB   │  │  AOF   │              │
│         │  快照  │  │  日志  │              │
│         └────┬───┘  └───┬────┘              │
│              ↓          ↓                     │
│         dump.rdb   appendonly.aof            │
└──────────────────────────────────────────────┘

3.1 RDB 快照持久化

RDB 工作原理

RDB(Redis DataBase)是将某个时间点的内存数据以快照的形式保存到磁盘文件中。

核心思想

时间 T1:  内存数据  拷贝一份  写入磁盘 (dump.rdb)
时间 T2:  继续处理写请求,不影响快照

RDB 文件示例

$ file dump.rdb
dump.rdb: data

$ hexdump -C dump.rdb | head
00000000  52 45 44 49 53 30 30 30  39 fa 09 72 65 64 69 73  |REDIS0009..redis|
00000010  2d 76 65 72 05 37 2e 30  2e 30 fa 0a 72 65 64 69  |-ver.7.0.0..redi|

SAVE vs BGSAVE

Redis 提供两种生成 RDB 文件的命令:

1️⃣ SAVE 命令(同步,阻塞)
127.0.0.1:6379> SAVE
OK  # 阻塞直到 RDB 文件生成完成

执行流程

┌─────────────────────────────────┐
│   Redis 主进程                   │
├─────────────────────────────────┤
│ 1. 收到 SAVE 命令               │
│ 2. ⛔ 停止处理所有客户端请求     │
│ 3. 遍历所有数据库               │
│ 4. 写入 dump.rdb 文件           │
│ 5. ✅ 恢复处理客户端请求         │
└─────────────────────────────────┘

# 期间所有命令都会被阻塞
# 适合关闭服务器前手动备份
2️⃣ BGSAVE 命令(异步,非阻塞)
127.0.0.1:6379> BGSAVE
Background saving started

执行流程

┌─────────────────────────────────┐
│   Redis 主进程                   │
├─────────────────────────────────┤
│ 1. 收到 BGSAVE 命令             │
│ 2. fork() 创建子进程            │
│ 3. ✅ 继续处理客户端请求         │
└─────────────┬───────────────────┘
              │
              ↓
┌─────────────────────────────────┐
│   Redis 子进程                   │
├─────────────────────────────────┤
│ 1. 遍历内存数据                 │
│ 2. 写入临时文件 temp-xxx.rdb    │
│ 3. 重命名为 dump.rdb            │
│ 4. 退出                         │
└─────────────────────────────────┘

SAVE vs BGSAVE 对比

特性SAVEBGSAVE
阻塞是(全程阻塞)否(fork 时短暂阻塞)
内存占用无额外占用子进程占用(COW)
性能影响大(停止服务)小(正常服务)
适用场景关闭服务前在线备份

COW 写时复制机制

BGSAVE 使用 Copy-On-Write(写时复制) 技术优化性能。

工作原理
1. fork() 创建子进程
   ┌──────────────┐      ┌──────────────┐
   │  父进程       │      │  子进程       │
   │  (Redis主进程)│      │  (BGSAVE)    │
   └──────┬───────┘      └──────┬───────┘
          │                      │
          └──────┬───────────────┘
                 ↓
           ┌──────────────┐
           │  内存数据     │
           │  (共享物理内存)│
           └──────────────┘

2. 父进程修改数据时,才复制该页
   ┌──────────────┐      ┌──────────────┐
   │  父进程       │      │  子进程       │
   │  (修改key1)   │      │  (读取key1)  │
   └──────┬───────┘      └──────┬───────┘
          │                      │
          ↓                      ↓
   ┌──────────────┐      ┌──────────────┐
   │  新内存页     │      │  原内存页     │
   │  key1=new    │      │  key1=old    │
   └──────────────┘      └──────────────┘

示例

# 1. 内存数据:10GB
# 2. BGSAVE fork 子进程
# 3. 期间只修改了 100MB 数据
# 4. 实际只复制 100MB 内存页
# 5. 总内存占用:10GB + 100MB

# 而不是:10GB + 10GB(全量复制)

COW 优势

  • 快速 fork:不需要复制全部内存
  • 节省内存:只复制修改的页
  • 不影响性能:父进程正常工作

注意事项

# 最坏情况:全部数据都被修改
# 内存占用:原数据 + 修改后的数据(接近 2 倍)

# 建议:
# 1. 物理内存 >= Redis 使用内存 * 2
# 2. 避免在 BGSAVE 期间大量写入

RDB 文件格式

┌─────────────────────────────────────────────┐
│             RDB 文件结构                     │
├─────────────────────────────────────────────┤
│ REDIS  │ 版本  │ 辅助   │ 数据库 │ EOF │ 校验 │
│ (5B)   │ (4B)  │ 字段   │ 数据   │(1B) │(8B)  │
└─────────────────────────────────────────────┘

详细结构:
┌────────────────┐
│ "REDIS0009"    │  魔数 + 版本号(Redis 7.0)
├────────────────┤
│ AUX 字段       │  redis-ver: 7.0.0
│                │  redis-bits: 64
│                │  ctime: 时间戳
│                │  used-mem: 内存占用
├────────────────┤
│ SELECT DB 0    │  选择数据库 0
├────────────────┤
│ RESIZE DB      │  数据库大小信息
│  • hash_size   │  键空间大小
│  • expires_size│  过期键数量
├────────────────┤
│ KEY-VALUE 数据 │
│  • type        │  对象类型
│  • key         │  键
│  • value       │  值
│  • expiretime  │  过期时间(可选)
├────────────────┤
│ SELECT DB 1    │  数据库 1(如果有)
│ ...            │
├────────────────┤
│ EOF (0xFF)     │  文件结束标志
├────────────────┤
│ CRC64 校验和   │  数据完整性校验
└────────────────┘

KEY-VALUE 编码示例

String 类型:
┌──────┬─────────┬───────────┐
│ type │  key    │  value    │
│  0"name""Redis"   │
└──────┴─────────┴───────────┘

List 类型:
┌──────┬─────────┬───────────────────┐
│ type │  key    │  value            │
│  1"list"  │ ["a", "b", "c"]   │
└──────┴─────────┴───────────────────┘

带过期时间:
┌────────────┬──────┬─────────┬───────────┐
│ expiretime │ type │  key    │  value    │
│ 16983072000"temp""value"   │
└────────────┴──────┴─────────┴───────────┘

触发机制

RDB 生成有多种触发方式:

1️⃣ 手动触发
# 同步保存(阻塞)
SAVE

# 异步保存(非阻塞)
BGSAVE

# 查看最后一次保存时间
LASTSAVE
2️⃣ 自动触发(配置文件)
# redis.conf
save 900 1      # 900秒内至少1次修改,触发 BGSAVE
save 300 10     # 300秒内至少10次修改
save 60 10000   # 60秒内至少10000次修改

# 满足任一条件即触发
# 多个 save 配置是"或"的关系

工作原理

// Redis 定时任务检查
void serverCron() {
    // 检查是否满足 save 条件
    for (int i = 0; i < server.saveparamslen; i++) {
        struct saveparam *sp = server.saveparams + i;
        
        // 距离上次保存的秒数
        time_t elapsed = server.unixtime - server.lastsave;
        
        // 自上次保存后的修改次数
        if (elapsed >= sp->seconds && server.dirty >= sp->changes) {
            // 触发 BGSAVE
            rdbSaveBackground();
        }
    }
}
3️⃣ 关闭服务器触发
# SHUTDOWN 命令会自动执行 SAVE
127.0.0.1:6379> SHUTDOWN
# 自动保存 RDB 文件后退出

# 等价于
127.0.0.1:6379> SAVE
127.0.0.1:6379> QUIT
4️⃣ 主从复制触发
# 从节点连接主节点时
# 主节点自动执行 BGSAVE 生成 RDB 文件
# 然后发送给从节点进行全量同步

# 主节点
INFO replication
# replication:
# ...
# rdb_bgsave_in_progress:1  ← 正在生成 RDB
5️⃣ FLUSHALL 触发
# 清空所有数据库
127.0.0.1:6379> FLUSHALL
# 会触发 RDB 保存(保存空数据库)

# 可以配置不保存
# redis.conf
save ""  # 禁用自动保存

优缺点分析

优点

1. 恢复速度快

# RDB 是二进制紧凑格式
# 加载速度远快于 AOF

# 10GB 数据恢复时间对比
RDB: 约 1 分钟
AOF: 约 30 分钟

2. 文件紧凑

# RDB 文件小
dump.rdb: 100 MB

# AOF 文件大(即使重写后)
appendonly.aof: 300 MB

3. 性能影响小

# BGSAVE 使用子进程
# 不影响主进程处理命令
# 只在 fork 时短暂阻塞(毫秒级)

4. 适合灾难恢复

# 可以定期备份 RDB 文件
# 保留多个时间点的快照

/data/redis/dump-20251026-120000.rdb
/data/redis/dump-20251026-180000.rdb
/data/redis/dump-20251027-000000.rdb
缺点

1. 数据丢失风险

# 场景:每 5 分钟保存一次 RDB
save 300 1

# 时间线
12:00:00  →  生成 RDB
12:04:59  →  服务器宕机 💥
# 丢失 5 分钟的数据(12:00 ~ 12:05)

# 适合对数据丢失容忍度高的场景

2. fork 可能阻塞

# 大内存实例 fork 耗时较长
# 10GB 数据 fork 可能需要 200-500ms

# 高并发场景下,会造成短暂卡顿

3. 文件恢复时间长(大数据量)

# 虽然比 AOF 快,但大数据量仍需时间
# 100GB 数据可能需要 10+ 分钟

4. CPU 密集

# 生成 RDB 需要遍历所有数据
# CPU 使用率较高
# 可能影响其他应用

3.2 AOF 追加文件持久化

AOF 工作原理

AOF(Append Only File)通过保存 Redis 服务器执行的写命令来记录数据库状态。

核心思想

客户端写命令 → 记录到 AOF 文件 → 重启时重新执行 → 恢复数据

AOF 文件示例

$ cat appendonly.aof
*3
$3
SET
$4
name
$5
Redis

*3
$4
HSET
$9
user:1001
$4
name
$5
Alice

*2
$3
DEL
$4
temp

AOF 写入流程

AOF 持久化分为三个步骤:

┌──────────────────────────────────────────────┐
│   1. 命令追加 (append)                        │
│      客户端命令 → AOF 缓冲区                  │
├──────────────────────────────────────────────┤
│   2. 文件写入 (write)                         │
│      AOF 缓冲区 → OS 缓冲区                   │
├──────────────────────────────────────────────┤
│   3. 文件同步 (fsync)                         │
│      OS 缓冲区 → 磁盘                         │
└──────────────────────────────────────────────┘
详细流程
// 1. 命令追加
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, 
                        robj **argv, int argc) {
    // 将命令转换为 RESP 协议格式
    sds buf = catAppendOnlyGenericCommand(argv, argc);
    
    // 追加到 AOF 缓冲区
    server.aof_buf = sdscatlen(server.aof_buf, buf, sdslen(buf));
}

// 2. 文件写入(每次事件循环)
void flushAppendOnlyFile(int force) {
    // 将 AOF 缓冲区写入 OS 缓冲区
    nwritten = write(server.aof_fd, server.aof_buf, sdslen(server.aof_buf));
    
    // 清空 AOF 缓冲区
    sdsclear(server.aof_buf);
    
    // 根据 fsync 策略同步到磁盘
    if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
        aof_fsync(server.aof_fd);  // 立即同步
    }
}

示例

# 客户端执行
SET key1 "value1"

# 1. 命令追加到 AOF 缓冲区
server.aof_buf = "*3\r\n$3\r\nSET\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n"

# 2. 写入 OS 缓冲区
write(aof_fd, server.aof_buf, len)

# 3. fsync 同步到磁盘(根据策略)
fsync(aof_fd)

fsync 策略

AOF 提供 3 种 fsync 策略,控制何时将数据从 OS 缓冲区同步到磁盘:

# redis.conf
appendfsync always      # 每次写命令都 fsync
appendfsync everysec    # 每秒 fsync 一次(默认)
appendfsync no          # 由操作系统决定(通常 30 秒)
1️⃣ always(最安全,性能最差)
每个写命令:
客户端命令 → AOF缓冲区 → OS缓冲区 → 💾 磁盘
                                    ↑
                               每次都 fsync

# 优点:数据最安全,最多丢失 1 条命令
# 缺点:性能最差(磁盘 IO 频繁)
# 适用:对数据安全要求极高的场景

性能影响

# 测试(10 万次 SET 操作)
appendfsync always:   约 300 ops/s    (磁盘限制)
appendfsync everysec: 约 60000 ops/s
appendfsync no:       约 80000 ops/s
2️⃣ everysec(推荐,平衡性能和安全)
每秒 fsync 一次:
客户端命令 → AOF缓冲区 → OS缓冲区 ─┐
                              ↓ 
                         后台线程每秒 fsync
                              ↓
                           💾 磁盘

# 优点:性能好,数据较安全
# 缺点:最多丢失 1 秒数据
# 适用:大多数场景(默认配置)

实现原理

// 后台线程每秒执行
void *bioProcessBackgroundJobs(void *arg) {
    while(1) {
        // 等待 1 秒
        sleep(1);
        
        // 执行 fsync
        if (server.aof_fsync == AOF_FSYNC_EVERYSEC) {
            aof_fsync(server.aof_fd);
        }
    }
}
3️⃣ no(性能最好,最不安全)
由操作系统决定:
客户端命令 → AOF缓冲区 → OS缓冲区 ─┐
                              ↓
                         OS 自己决定
                         (通常 30 秒)
                              ↓
                           💾 磁盘

# 优点:性能最好
# 缺点:可能丢失 30 秒数据
# 适用:对数据丢失容忍度高的场景
策略对比
策略性能数据安全可能丢失适用场景
always最高1 条命令金融、支付
everysec1 秒数据大多数场景(推荐)
no最好30 秒数据对丢失容忍度高

AOF 重写机制

AOF 文件会随着写命令不断增大,Redis 提供 AOF 重写机制压缩文件。

为什么需要重写?
# 原始 AOF 文件(冗余命令)
SET key 1
SET key 2
SET key 3
SET key 4
DEL key
LPUSH list a
LPUSH list b
LPUSH list c

# 重写后的 AOF 文件(只保留最终状态)
# key 已删除,不需要记录
LPUSH list c b a  # 合并为一条命令

空间节省示例

# 重写前
appendonly.aof: 500 MB  (100 万条命令)

# 重写后
appendonly.aof: 50 MB   (只有最终状态)
重写实现

BGREWRITEAOF 命令

127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started

工作流程

┌─────────────────────────────────┐
│   Redis 主进程                   │
├─────────────────────────────────┤
│ 1. 收到 BGREWRITEAOF 命令       │
│ 2. fork() 创建子进程            │
│ 3. 继续处理客户端请求           │
│ 4. 新命令写入:                 │
│    • 旧 AOF 文件                │
│    • AOF 重写缓冲区             │
└──────────┬──────────────────────┘
           │
           ↓
┌─────────────────────────────────┐
│   Redis 子进程                   │
├─────────────────────────────────┤
│ 1. 遍历内存数据库               │
│ 2. 生成新的 AOF 文件            │
│    temp-rewriteaof-xxx.aof      │
│ 3. 通知父进程完成               │
│ 4. 退出                         │
└──────────┬──────────────────────┘
           │
           ↓
┌─────────────────────────────────┐
│   Redis 主进程                   │
├─────────────────────────────────┤
│ 1. 将 AOF 重写缓冲区追加到      │
│    新 AOF 文件                  │
│ 2. 重命名新文件为               │
│    appendonly.aof               │
│ 3. 关闭旧文件,使用新文件       │
└─────────────────────────────────┘

示例

# 重写过程
12:00:00  子进程开始重写(遍历内存数据)
12:00:05  主进程收到新写命令 SET key1 100
          → 写入旧 AOF 文件
          → 写入 AOF 重写缓冲区
12:00:10  子进程完成重写(temp-xxx.aof)
12:00:11  主进程将重写缓冲区追加到新文件
12:00:12  重命名新文件,替换旧文件
自动触发重写
# redis.conf

# AOF 文件大小超过上次重写后的 100%,触发重写
auto-aof-rewrite-percentage 100

# AOF 文件最小 64MB 才触发重写(避免频繁重写小文件)
auto-aof-rewrite-min-size 64mb

触发条件

// 同时满足两个条件
if (server.aof_current_size > server.aof_rewrite_min_size &&
    server.aof_current_size > server.aof_rewrite_base_size * 
    (1 + server.aof_rewrite_perc / 100.0)) {
    // 触发 BGREWRITEAOF
}

示例

# 上次重写后 AOF 文件:100 MB
# 当前 AOF 文件:200 MB
# 200 > 100 * 2  ✅ 触发重写

# 重写后:80 MB
# 下次触发:160 MB

优缺点分析

优点

1. 数据更安全

# everysec 策略最多丢失 1 秒数据
# always 策略最多丢失 1 条命令

# 远好于 RDB(可能丢失数分钟数据)

2. 可读性好

# AOF 文件是文本格式
$ cat appendonly.aof
*3
$3
SET
$4
name
$5
Redis

# 可以直接查看、修改、分析

3. 误操作可恢复

# 场景:误执行 FLUSHALL

# 1. 停止 Redis
$ redis-cli SHUTDOWN NOSAVE

# 2. 编辑 AOF 文件,删除 FLUSHALL 命令
$ vi appendonly.aof
# 删除最后的 FLUSHALL

# 3. 重启 Redis
$ redis-server redis.conf

# 数据恢复!

4. 自动重写压缩

# 自动压缩 AOF 文件
# 不需要人工干预
缺点

1. 文件体积大

# 即使重写后,AOF 文件仍比 RDB 大

RDB:  100 MB  (二进制紧凑格式)
AOF:  300 MB  (文本格式 + 命令冗余)

2. 恢复速度慢

# 需要逐条执行命令
# 大文件恢复耗时长

# 10GB 数据恢复对比
RDB:  约 1 分钟
AOF:  约 30 分钟

3. 性能影响大

# 写入性能(always 策略)
appendfsync always:   约 300 ops/s
RDB:                  约 80000 ops/s

# 磁盘 IO 频繁

4. 可能产生 bug

# 历史上 AOF 重写存在 bug
# 导致数据丢失或不一致

# 建议:
# 1. 使用稳定版本
# 2. 同时开启 RDB 和 AOF

3.3 混合持久化

混合持久化原理

Redis 4.0 引入混合持久化,结合 RDB 和 AOF 的优点。

核心思想

AOF 文件 = RDB 格式数据 + AOF 格式增量命令

┌──────────────────────────────────┐
│   混合 AOF 文件                   │
├──────────────────────────────────┤
│  [RDB 格式]                       │
│  • 内存快照(二进制)             │
│  • 快速加载                       │
├──────────────────────────────────┤
│  [AOF 格式]                       │
│  • 增量写命令(文本)             │
│  • 自上次重写后的命令             │
└──────────────────────────────────┘

工作流程

1. AOF 重写时
   ┌─────────────────────┐
   │  子进程              │
   ├─────────────────────┤
   │ 遍历内存数据         │
   │ ↓                   │
   │ 以 RDB 格式写入     │ ← 快速、紧凑
   │ ↓                   │
   │ 生成 temp-xxx.aof   │
   └─────────────────────┘

2. 日常写入
   ┌─────────────────────┐
   │  主进程              │
   ├─────────────────────┤
   │ 新的写命令           │
   │ ↓                   │
   │ 以 AOF 格式追加     │ ← 增量命令
   │ ↓                   │
   │ appendonly.aof      │
   └─────────────────────┘

3. 数据恢复
   ┌─────────────────────┐
   │  Redis 启动          │
   ├─────────────────────┤
   │ 1. 加载 RDB 部分    │ ← 快速
   │ 2. 执行 AOF 部分    │ ← 完整
   └─────────────────────┘

配置与使用

# redis.conf

# 开启 AOF
appendonly yes

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

查看混合 AOF 文件

$ hexdump -C appendonly.aof | head
00000000  52 45 44 49 53 30 30 30  39 fa 09 72 65 64 69 73  |REDIS0009..redis|
                                                              ↑ RDB 魔数

$ tail appendonly.aof
*3
$3
SET
$4
key1
$5
value

优势分析

特性RDBAOF混合持久化
恢复速度较快 ✅
文件大小中 ✅
数据完整性好 ✅
性能影响中 ✅

混合持久化优势

  1. 快速恢复:RDB 部分快速加载
  2. 数据完整:AOF 部分保证完整性
  3. 文件紧凑:RDB 格式比纯 AOF 小
  4. 兼容性好:可以关闭混合模式回退到纯 AOF

3.4 持久化策略选择

性能对比

写入性能(10 万次 SET 操作):

配置QPS说明
无持久化80000基准性能
RDB (save 60 10000)78000几乎无影响
AOF (everysec)60000性能下降 25%
AOF (always)300性能下降 99%+
RDB + AOF60000同 AOF everysec

文件大小(10GB 数据):

持久化方式文件大小压缩率
RDB8 GB最小
AOF(重写后)15 GB中等
混合持久化10 GB较小

恢复速度(10GB 数据):

持久化方式恢复时间
RDB1-2 分钟
AOF20-30 分钟
混合持久化3-5 分钟

数据安全性对比

持久化方式可能丢失的数据适用场景
无持久化全部数据纯缓存
RDB (save 300 10)5 分钟数据允许丢失部分数据
AOF (everysec)1 秒数据一般业务(推荐)
AOF (always)最多 1 条命令金融、支付
RDB + AOF1 秒数据 + 双重保障重要业务
混合持久化1 秒数据平衡方案(推荐)

使用场景建议

1️⃣ 纯缓存场景
# 配置
save ""                 # 禁用 RDB
appendonly no           # 禁用 AOF

# 适用
# • 缓存数据,可以从数据库重建
# • 对数据丢失容忍度高
# • 追求极致性能
2️⃣ 一般业务场景(推荐)
# 配置
appendonly yes
aof-use-rdb-preamble yes      # 混合持久化
appendfsync everysec          # 每秒 fsync

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

save ""                       # 禁用 RDB(AOF 已足够)

# 适用
# • 大多数业务
# • 平衡性能和安全
# • 最多丢失 1 秒数据
3️⃣ 高可用场景
# 配置(双重保障)
# RDB
save 900 1
save 300 10
save 60 10000

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

# 适用
# • 重要业务数据
# • 需要双重保障
# • RDB 用于快速恢复,AOF 保证数据完整
4️⃣ 金融/支付场景
# 配置(最高安全)
appendonly yes
appendfsync always            # 每条命令都 fsync
aof-use-rdb-preamble yes

# 适用
# • 金融支付
# • 不允许数据丢失
# • 可以接受性能下降
5️⃣ 主从复制场景
# 主节点
save 900 1
save 300 10
appendonly yes
appendfsync everysec

# 从节点(只读)
save ""                       # 从节点可以不开启持久化
appendonly no                 # 数据从主节点同步

3.5 数据恢复流程

恢复优先级

Redis 启动时按以下优先级恢复数据:

┌─────────────────────────────────┐
│   Redis 启动                     │
└─────────────┬───────────────────┘
              ↓
        AOF 存在?
         ├─ 是 ───→ 加载 AOF 文件
         │          (优先级更高)
         └─ 否
              ↓
        RDB 存在?
         ├─ 是 ───→ 加载 RDB 文件
         └─ 否
              ↓
        空数据库启动

原因

AOF 优先级更高,因为:
1. AOF 数据更完整(最多丢失 1 秒)
2. RDB 可能丢失更多数据(数分钟)

# 如果同时存在 AOF 和 RDB
# Redis 会忽略 RDB,只加载 AOF

恢复步骤

1️⃣ AOF 恢复
# 1. 停止 Redis
$ redis-cli SHUTDOWN

# 2. 检查 AOF 文件
$ redis-check-aof appendonly.aof
AOF analyzed: filename=appendonly.aof, size=1048576, ok_up_to=1048576, ok_up_to_line=10000, diff=0

# 3. 如果文件损坏,修复
$ redis-check-aof --fix appendonly.aof
# 会删除损坏部分后的所有数据

# 4. 启动 Redis
$ redis-server redis.conf
# 自动加载 AOF 文件
2️⃣ RDB 恢复
# 1. 停止 Redis
$ redis-cli SHUTDOWN

# 2. 将 RDB 文件复制到 Redis 数据目录
$ cp /backup/dump.rdb /var/lib/redis/dump.rdb

# 3. 确保文件权限
$ chown redis:redis /var/lib/redis/dump.rdb

# 4. 启动 Redis
$ redis-server redis.conf
# 自动加载 RDB 文件
3️⃣ 混合持久化恢复
# 混合 AOF 文件包含 RDB 和 AOF 两部分
# Redis 自动识别并正确加载

$ redis-server redis.conf
# 1. 识别 AOF 文件是混合格式
# 2. 加载 RDB 部分(快速)
# 3. 执行 AOF 部分(增量命令)

恢复异常处理

AOF 文件损坏
# 错误信息
Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>

# 解决方案
$ redis-check-aof --fix appendonly.aof
# 删除损坏部分后的数据
RDB 文件损坏
# 错误信息
Short read or OOM loading DB. Unrecoverable error, aborting now.

# 解决方案
1. 使用备份的 RDB 文件
2. 或者删除 RDB 文件,从 AOF 恢复
恢复时间过长
# 场景:100GB AOF 文件,恢复需要 1+ 小时

# 优化方案
1. 使用 RDB 文件(如果可接受数据丢失)
2. 使用混合持久化(更快)
3. 增加服务器内存,加快加载速度

持久化最佳实践

1️⃣ 推荐配置(生产环境)

# redis.conf

# ============ 持久化配置 ============

# AOF(推荐开启)
appendonly yes
aof-use-rdb-preamble yes       # 混合持久化
appendfsync everysec           # 每秒 fsync
appendfilename "appendonly.aof"

# AOF 重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes         # 加载截断的 AOF 文件

# RDB(可选,用于备份)
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb

# 数据目录
dir /var/lib/redis

2️⃣ 定期备份

# 备份脚本
#!/bin/bash

DATE=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR=/backup/redis

# 创建备份目录
mkdir -p $BACKUP_DIR

# 备份 RDB 文件
cp /var/lib/redis/dump.rdb $BACKUP_DIR/dump-$DATE.rdb

# 备份 AOF 文件
cp /var/lib/redis/appendonly.aof $BACKUP_DIR/appendonly-$DATE.aof

# 压缩备份
tar -czf $BACKUP_DIR/redis-backup-$DATE.tar.gz $BACKUP_DIR/*-$DATE.*

# 删除 7 天前的备份
find $BACKUP_DIR -name "redis-backup-*.tar.gz" -mtime +7 -delete

echo "Backup completed: $DATE"

定时任务

# crontab -e
# 每天凌晨 2 点备份
0 2 * * * /opt/scripts/redis-backup.sh

3️⃣ 监控持久化状态

# 查看持久化状态
127.0.0.1:6379> INFO persistence
# Persistence
loading:0
current_cow_size:0
current_cow_size_age:0
current_fork_perc:0.00
current_save_keys_processed:0
current_save_keys_total:0
rdb_changes_since_last_save:100         # 自上次保存后的修改
rdb_bgsave_in_progress:0                # 是否正在 BGSAVE
rdb_last_save_time:1698307200           # 上次保存时间
rdb_last_bgsave_status:ok               # 上次 BGSAVE 状态
rdb_last_bgsave_time_sec:5              # 上次 BGSAVE 耗时
aof_enabled:1                           # AOF 是否开启
aof_rewrite_in_progress:0               # 是否正在重写
aof_last_rewrite_time_sec:10            # 上次重写耗时
aof_current_size:1048576                # 当前 AOF 文件大小
aof_base_size:524288                    # 上次重写后的大小

4️⃣ 性能优化

# 1. 避免 fork 阻塞
# 减少内存使用,加快 fork 速度
maxmemory 8gb
maxmemory-policy allkeys-lru

# 2. 使用 SSD
# AOF 频繁写磁盘,SSD 性能更好

# 3. 避免大实例
# 建议单实例内存 < 10GB
# 大数据量使用集群

# 4. 调整重写阈值
# 避免频繁重写
auto-aof-rewrite-min-size 128mb  # 提高到 128MB

# 5. 监控 fork 时间
# 如果 fork 耗时过长,考虑缩小实例

5️⃣ 灾难恢复预案

# 1. 双机备份
# 主服务器 + 从服务器 + 离线备份

# 2. 异地备份
# 定期将备份文件同步到异地

# 3. 恢复演练
# 定期演练数据恢复流程

# 4. 监控告警
# 持久化失败时及时告警

常见问题解答

Q1: RDB 和 AOF 应该选哪个?

A: 推荐同时开启(混合持久化)。

  • 仅 RDB:数据丢失风险大
  • 仅 AOF:恢复速度慢
  • RDB + AOF:兼顾性能和安全(推荐)

Q2: appendfsync 应该选哪个?

A: 大多数场景选 everysec(默认)。

  • always:金融/支付场景
  • everysec:一般业务(推荐)
  • no:纯缓存场景

Q3: fork 导致的阻塞如何优化?

A:

# 1. 减少内存使用
maxmemory 8gb

# 2. 使用更快的 CPU
# fork 是 CPU 密集操作

# 3. 避免大实例
# 建议单实例 < 10GB

# 4. 监控 fork 耗时
INFO stats | grep fork

Q4: AOF 文件过大怎么办?

A: 触发 AOF 重写。

# 手动重写
BGREWRITEAOF

# 调整自动重写阈值
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

Q5: 如何选择备份策略?

A:

# 开发环境
save ""            # 不持久化
appendonly no

# 生产环境
appendonly yes     # AOF
save 900 1         # RDB(用于备份)

# 定期备份到异地存储

总结

本文深入剖析了 Redis 的三种持久化机制:

RDB 快照

  • ✅ 优点:恢复快、文件小、性能影响小
  • ❌ 缺点:可能丢失数据、fork 阻塞
  • 🎯 适用:全量备份、主从同步

AOF 日志

  • ✅ 优点:数据安全、可读性好、可恢复误操作
  • ❌ 缺点:文件大、恢复慢、性能影响大
  • 🎯 适用:数据安全要求高

混合持久化

  • ✅ 优点:快速恢复、数据完整、文件紧凑
  • ❌ 缺点:需要 Redis 4.0+
  • 🎯 适用:生产环境(推荐)

最佳实践

  1. ✅ 生产环境开启混合持久化
  2. ✅ 使用 appendfsync everysec
  3. ✅ 定期备份到异地
  4. ✅ 监控持久化状态
  5. ✅ 定期演练恢复流程

理解持久化机制,能帮助你:

  • ✅ 保证数据安全
  • ✅ 优化性能
  • ✅ 快速恢复故障
  • ✅ 制定合理的备份策略

💡 下一篇预告:《Redis 事件驱动与线程模型:Reactor 模式与 IO 多线程》

敬请期待!