Redis持久化

Redis 的持久化机制本质是:把内存数据在某个时机“落盘”到磁盘,保证重启后数据不丢失。核心有两种方案:

  • RDB(快照)
  • AOF(追加日志)

以及它们的组合使用。


一、RDB(Redis DataBase 快照)

1. 核心思想

在某一时刻,把内存中的全量数据生成一个快照文件(.rdb),直接写入磁盘。

类似:数据库“拍照存档”


2. 触发方式

(1)自动触发(配置)

1
2
3
save 900 1     # 900秒内有1次修改就触发
save 300 10 # 300秒内有10次修改
save 60 10000 # 60秒内有10000次修改

(2)手动触发

1
2
SAVE    # 同步阻塞(不推荐)
BGSAVE # 异步(常用)

3. 执行流程(重点⭐)

当执行 BGSAVE

  1. Redis 主进程调用 fork()
  2. 创建子进程(复制当前内存数据)
  3. 子进程负责写 RDB 文件
  4. 主进程继续处理请求

利用的是:

  • 写时复制(Copy-On-Write)机制

4. 优点

  • 文件体积小(压缩存储)
  • 恢复速度快(直接加载快照)
  • 对性能影响小(子进程执行)

5. 缺点

  • 数据可能丢失(最后一次快照之后的数据)
  • fork 时内存开销大
  • 不适合高实时性要求场景

6. 适用场景

  • 数据允许少量丢失
  • 更看重恢复速度
  • 用于备份 / 灾难恢复

二、AOF(Append Only File)

1. 核心思想

每一条写操作命令记录下来,按顺序追加到日志文件中。

类似:数据库“操作日志”


2. 记录内容示例

1
2
3
SET name hansen
INCR count
DEL key1

3. 同步策略(fsync)⚠️关键点

AOF 的核心在于:什么时候写入磁盘

三种策略:

策略 说明 性能 安全性
always 每次写都刷盘 ❌ 最慢 ✅ 最安全
everysec(默认) 每秒刷盘 ⚖️ 平衡 ⚖️
no 由OS决定 ✅ 最快 ❌ 最不安全

4. 执行流程

写命令执行流程:

1
2
3
4
5
6
7
客户端写请求

执行命令(内存)

追加到 AOF buffer

根据策略刷盘

5. AOF 重写(Rewrite)⭐

为什么需要?

AOF 会越来越大,比如:

1
2
3
SET a 1
SET a 2
SET a 3

👉 实际只需要:

1
SET a 3

重写机制:

Redis 会:

  1. fork 子进程
  2. 根据当前内存数据生成“最简命令集”
  3. 写入新的 AOF 文件
  4. 替换旧文件

6. 优点

  • 数据更安全(最多丢1秒)
  • 可读性强(日志)
  • 支持重写压缩

7. 缺点

  • 文件比 RDB 大
  • 恢复速度慢(需要重放命令)
  • 写入性能略低

8. 适用场景

  • 对数据一致性要求高
  • 不希望数据丢失

三、RDB vs AOF 对比

维度 RDB AOF
数据完整性 ❌ 可能丢数据 ✅ 更安全
文件大小 ✅ 小 ❌ 大
恢复速度 ✅ 快 ❌ 慢
性能影响 ✅ 小 ❌ 稍大
可读性 ❌ 二进制 ✅ 文本

四、混合持久化(推荐)

Redis 4.0 引入:

RDB + AOF 混合模式

机制

AOF 重写时:

  • 前半部分:RDB 快照
  • 后半部分:增量 AOF

优势

  • 启动速度快(RDB)
  • 数据更安全(AOF)
  • 文件更小

开启方式

1
aof-use-rdb-preamble yes

五、Redis 启动加载顺序

当 Redis 重启时:

  1. 优先加载 AOF
  2. 如果没有 AOF → 使用 RDB

原因:AOF 数据更完整


六、面试总结(高频模板)

可以这样回答:

Redis 提供两种持久化机制:RDB 和 AOF。

RDB 是通过生成数据快照来持久化,适合做备份,恢复快但可能丢数据;
AOF 是通过记录写命令日志来持久化,数据更安全但文件更大、恢复较慢。

AOF 提供三种刷盘策略(always、everysec、no),通常使用 everysec,在性能和安全之间做平衡。

此外 Redis 4.0 引入了混合持久化,结合了 RDB 和 AOF 的优点,既保证恢复速度又提高数据安全性。

在实际生产中,一般会同时开启 RDB 和 AOF。


七、fork + Copy-On-Write 原理(🔥高频)

fork + Copy-On-Write(写时复制) = 创建子进程时不复制内存,只有发生写操作时才真正复制数据页


1. fork 原理(进程复制)

在 Linux 中,fork() 会创建一个子进程:

1
pid_t pid = fork();

fork 后的特点:

  • 子进程 ≈ 父进程的“副本”

  • 共享:

    • 代码段
    • 数据段
    • 堆、栈
  • 逻辑上独立,物理上共享内存页(关键)


2. Copy-On-Write(写时复制)机制

核心思想

不立即复制内存,而是“延迟复制”


工作机制

fork 刚发生时:

1
2
3
父进程 ----\
---> 共享同一块物理内存
子进程 ----/
  • 两个进程指向同一份内存页
  • 内存页被标记为:只读

当发生写操作时:

  • 情况1:父进程修改数据
1
2
3
4
父进程写数据 → 触发缺页异常
→ 复制该内存页
→ 父进程使用新页
→ 子进程仍用旧页
  • 情况2:子进程修改数据(少见)

同理也会复制一份


图示总结:

1
2
3
4
5
6
7
初始:
父进程 → [Page A]
子进程 → [Page A]

写入后:
父进程 → [Page A']
子进程 → [Page A]

关键点总结

  • ✅ 读操作:共享内存
  • ❗ 写操作:触发复制
  • ✅ 粒度:页级(通常4KB)

3. 一句话总结(八股版)

fork 创建子进程时不会复制内存,而是通过 Copy-On-Write 共享内存页,只有在发生写操作时才复制对应页,从而大幅降低持久化的开销,但在高写入场景下可能导致内存暴涨。


八、AOF 重写期间如何保证数据一致

AOF 重写(BGREWRITEAOF)流程:

  1. fork 子进程
  2. 子进程根据当前内存生成新的 AOF 文件
  3. 主进程继续处理写请求

问题来了:

1
2
3
fork 之后产生的新写操作
❓子进程看不到(因为是旧内存)
❓如果不处理 → 数据丢失

1. 解决核心:双缓冲 + 重写缓冲区

Redis 使用的是:

AOF buffer + AOF rewrite buffer(重写缓冲区)


2. 完整流程(重点⭐)

1. fork 发生

  • 主进程 → 正常处理请求
  • 子进程 → 开始生成新的 AOF 文件(基于旧数据)

2. 主进程处理写请求(关键)

当有新写操作:

1
SET key value

Redis 会做 两件事

写入 AOF buffer(正常流程)

用于旧 AOF 文件

同时写入 rewrite buffer(关键)

用于“补偿”新 AOF 文件


3. 子进程完成重写

  • 生成一个新的 AOF 文件(只包含旧数据的最简命令)

4. 最关键一步:数据合并

子进程完成后:

  1. 主进程把 rewrite buffer 中的数据
  2. 追加到 新 AOF 文件末尾

这样:

1
新 AOF = 重写生成的数据 + 重写期间的增量数据

5. 原子替换

最后:

  • 用新 AOF 文件替换旧 AOF(rename 原子操作)

数据一致性保证完成


3. 流程图总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
        fork

┌───────────────┐
│ 子进程 │ → 写新 AOF(旧数据)
└───────────────┘


┌───────────────┐
│ 主进程 │
│ │
│ 写请求 │
│ ↓ │
│ AOF buffer │
│ rewrite buffer│
└───────────────┘

子进程完成 → 合并 rewrite buffer → 替换文件

4. 关键设计点总结 ⭐

1. 为什么需要 rewrite buffer?

因为:

  • 子进程基于旧数据
  • 无法感知新写入

2. 为什么写两份?

buffer 作用
AOF buffer 保障旧 AOF 正常写入
rewrite buffer 保障新 AOF 不丢数据

5. 面试标准回答模板

在 AOF 重写期间,Redis 通过维护一个 AOF 重写缓冲区来保证数据一致性。
fork 子进程后,子进程基于当前内存生成新的 AOF 文件,而主进程继续处理写请求。
对于重写期间的新写操作,Redis 会同时写入 AOF buffer 和 AOF rewrite buffer。
当子进程完成重写后,主进程会将 rewrite buffer 中的增量数据追加到新 AOF 文件末尾,最后通过原子 rename 替换旧文件,从而保证数据不丢失且一致。