This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
2026-05-22 00:25:59 +08:00

518 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [Redis, 缓存, 持久化, RDB]
create time: 2026-05-15 18:12
---
# RDB 持久化
## 概述
> [!QUESTION] Redis 的数据都存在内存里,那服务器一断电,数据岂不是全没了?
> 没错——这就是**持久化**存在的意义。Redis 提供两种持久化方案:**RDB** 和 **AOF**,本文先讲 RDB。
> [!SUMMARY] 一句话理解 RDB
> RDB(Redis Database)就是在**某个时间点**,把 Redis 内存中的**全部数据**"拍一张照片"存到磁盘上——这个"照片"就是一个 `.rdb` **快照文件**。
>
> 你可以把它想象成**游戏存档**:打 Boss 之前你手动存了一次档,万一翻车了可以读档重来。但代价是——**存档之后到翻车之间**的操作全丢了。
### 核心特点
| 特性 | 说明 | 类比 |
|------|------|------|
| ⚡ 恢复极快 | 二进制文件直接加载到内存,跳过命令回放 | 像从压缩包解压,比照着菜谱重新做菜快得多 |
| 📦 文件紧凑 | 使用 LZF 压缩,远小于等量 AOF 日志 | 一张照片的信息量 > 千言万语的描述 |
| 🔧 架构简单 | 无额外复杂逻辑,一个文件搞定备份/迁移 | "复制-粘贴"级别的简单 |
| ⚠️ 数据窗口丢失 | 两次快照之间的写操作不可恢复 | 存档点之后的操作无法回溯 |
### 何时使用
- **大数据集冷备**:定期全量备份到 S3 / OSS,作为最后一道防线
- **灾难恢复**:服务器挂了?把 `.rdb` 文件丢到新机器上,秒级恢复
- **大规模数据场景**:当你的 Redis 存了 50GB 数据,AOF 回放可能要几十分钟,RDB 几秒搞定
- **主从复制**:新从节点首次全量同步时,主节点会自动触发 RDB 快照传给从节点
> [!QUESTION] 为什么 RDB 恢复比 AOF 快?
> 打个比方:RDB 就像**拍了一张照片**,加载时直接"看照片"还原场景;AOF 则像**记了一本流水账**("先加了张三,再删了李四,又改了王五……"),恢复时需要从头到尾把账本念一遍——数据量越大,"念账本"的时间就越长。
## 触发机制
### 手动触发
```bash
# 当前线程执行,阻塞主线程 ❌ 生产环境禁用!
SAVE
# 主进程 fork 子进程在后台完成(✅ 推荐)
BGSAVE
# 查看上次 BGSAVE 成功的时间戳
LASTSAVE
```
| 命令 | 行为 | 适用场景 | 风险 |
|------|------|----------|------|
| `SAVE` | 同步阻塞,直到快照完成 | 紧急调试,或确认实例无流量时 | **主线程完全卡死**——所有客户端请求排队等待 |
| `BGSAVE` | fork 子进程异步完成 | 生产环境日常备份 | fork 瞬间有极短暂的阻塞(通常毫秒级) |
> [!QUESTION] 为什么 SAVE 危险但 Redis 还要提供它?
> 因为 `SAVE` 可以在 **BGSAVE 正在执行时** 强制同步保存(BGSAVE 期间再次 BGSAVE 会被拒绝)。这是一个极端场景的"保底"手段。
### 自动触发——条件持久化
通过 `redis.conf` 配置多条 `save` 规则,Redis 用一个**"脏键计数器"(dirty counter)** 来追踪:
```conf
save 900 1 # 900 秒内至少有 1 个 key 被修改 → 触发快照
save 300 10 # 300 秒内至少有 10 个 key 被修改
save 60 10000 # 60 秒内至少有 10000 个 key 被修改
```
> [!INFO] 底层是怎么工作的?
> Redis 内部维护两个计数器:
> 1. **`dirty` 计数器**:自上次 BGSAVE 成功以来,总共被修改的 key 数量(每次写操作 +1)
> 2. **`lastsave` 时间戳**:上次成功完成 BGSAVE 的 Unix 时间戳
>
> 每隔 100ms(`serverCron` 定时任务),Redis 会检查:**当前时间 - lastsave ≥ 阈值时间 && dirty ≥ 阈值次数**,两个条件**同时满足**才会触发 BGSAVE。
>
> 所以 `save 900 1` 的意思是:"距离上次快照超过 900 秒**并且**期间有至少 1 个 key 被修改过"。如果 900 秒内没有任何写操作,即使规则匹配了也不会触发——因为没有新数据需要保存。
> [!QUESTION] 为什么要设三档而不是一档?
> 这是一个**渐进式频率**设计:
> - **低活跃度**(900s/1key):写入很少时,15 分钟存一次就够了——即使丢了 15 分钟的数据,也几乎没什么损失
> - **中活跃度**(300s/10key):中等写入压力时,5 分钟存一次,平衡了 I/O 开销和数据安全
> - **高活跃度**(60s/10000key):大量写入时,1 分钟存一次——虽然 fork 更频繁了,但此时丢 1 分钟的数据损失也更大
>
> 本质上是"**数据越重要、变更越频繁,保存就越勤**"的策略。
```bash
# 运行时动态修改(覆盖所有规则)
CONFIG SET save "900 1 300 10 60 10000"
# 临时关闭自动快照(谨慎!相当于禁用 RDB 自动触发)
CONFIG SET save ""
```
> [!WARNING] CONFIG SET 不持久化
> `CONFIG SET` 仅在内存中生效,Redis 重启后会恢复 `redis.conf` 的配置。若要永久生效,必须修改 `redis.conf` 并执行 `CONFIG REWRITE` 或手动重启。
### 其他自动触发场景
除了 `save` 规则外,以下场景也会自动触发 RDB:
| 场景 | 说明 | 为什么需要? |
|------|------|-------------|
| **正常关闭** | 执行 `SHUTDOWN` 时自动触发一次 BGSAVE,保证关闭前数据落盘 | 优雅关闭 ≠ 数据丢失 |
| **主从全量同步** | 新从节点首次连接主节点时,主节点自动触发 BGSAVE 并将快照传给从节点 | 从节点需要一份完整的数据副本 |
| **AOF 重写** | 开启混合持久化后,`BGREWRITEAOF` 会先写 RDB 快照作为基线 | 后面"混合持久化"章节详解 |
> [!TIP] Redis 如何避免"撞车"?
> Redis 同一时刻**只会有一个子进程在做持久化**。如果 BGSAVE 正在执行,此时再触发 SAVE/BGSAVE:
> - `BGSAVE` → 直接返回错误 `ERR Background save already in progress`
> - `SAVE` → 会等待 BGSAVE 完成后再执行
>
> 这个设计避免了多个子进程争抢 I/O 资源。
## RDB 文件结构
可以把 `.rdb` 文件想象成一个**快递包裹**:有快递单号(Header)、多个包裹(DB 里的数据)、包裹拆完的标记(EOF)、以及签收校验码(Footer)。
```mermaid
flowchart TD
subgraph header["1 HEADER 文件头"]
A["Magic: REDIS07 + Version"]
B["Auxiliary Fields: redis-ver, ctime, used-mem"]
C["DB Selector: FE 00(选择 DB 0)"]
A --> B --> C
end
subgraph data["2 DATA 数据段(每个非空 DB 重复一次)"]
D["Key Type: 1 byte(String=0, List=1, Set=2 ...)"]
E["Expire Info: 可选, TTL 时间戳"]
F["Encoded Key + Value: 直接序列化底层数据结构"]
D --> E --> F
F -.-> G["... 重复 N 个 Key-Value 对 ..."]
end
subgraph footer["3 FOOTER 文件尾"]
H["EOF Marker: 0xFF(数据结束)"]
I["CRC64 Checksum: 8 bytes(完整性校验)"]
H --> I
end
header --> data --> footer
```
### 各段含义详解
**1️⃣ Header(文件头)**
- **Magic 字符串**:以 `REDIS` 开头 + 4 字节版本号(如 `REDIS0009`),用于标识"这是一个 RDB 文件"
- **辅助字段(Auxiliary Fields)**:记录 Redis 版本、创建时间、内存占用等元信息——**不是数据本身,只是"快递单信息"**
- **DB Selector**:`FE` + DB 编号,告诉你"接下来的数据属于哪个 DB"(默认只有 DB 0 有数据,所以通常只出现一次)
**2️⃣ Data(数据段)**
每个 key-value 对的存储格式:
| 字段 | 大小 | 说明 |
|------|------|------|
| **Key Type** | 1 byte | 值的类型标记:String=0, List=1, Set=2, ZSet=3, Hash=4 … |
| **Expire Time** | 0 或 8 bytes | 如果 key 设置了过期时间,会附带一个毫秒级时间戳 |
| **Key** | 变长 | key 名(字符串编码) |
| **Value** | 变长 | **直接序列化底层数据结构**(ziplist、intset、skiplist 等),这是 RDB 文件紧凑的关键 |
**3️⃣ Footer(文件尾)**
- **EOF 标记**:`0xFF`,一个字节,宣告"数据到这里结束了"
- **CRC64 校验和**:8 字节,Redis 加载文件时用它校验完整性——如果校验失败,说明文件损坏,加载直接报错
> [!QUESTION] 为什么 RDB 文件比 AOF 小很多?
> 核心在于存储方式不同。举个例子:
>
> 假设你用 `HSET user:1 name "Alice" age 25 city "Beijing"` 存了一个 Hash:
> - **AOF 记录的是**:这条命令字符串本身 → `*8\r\n$5\r\nHSET\r\n$6\r\nuser:1\r\n$4\r\nname\r\n...\` 约 **120+ 字节**
> - **RDB 记录的是**:Hash 类型标记 + ziplist 编码的紧凑字节流 → 约 **30-40 字节**
>
> 差距来自:RDB 不记录"怎么做的",只记录"结果是什么"——就像一张照片远比描述照片的文字更紧凑。
## 关键配置参数
```conf
# ---- RDB 核心参数 ----
dbfilename dump.rdb # 快照文件名(建议保留默认)
dir /var/lib/redis # 工作目录——RDB/AOF 文件都存放在这里
rdbcompression yes # 是否使用 LZF 压缩字符串
rdbchecksum yes # 是否在文件末尾写入 CRC64 校验
rdb-save-incremental-fsync yes # 写入磁盘时是否增量 fsync
rdb-del-sync-files no # 无盘复制时,是否删除用于同步的临时 RDB(Redis 7+)
```
### 参数逐个解释
| 参数 | 推荐值 | 为什么? |
|------|--------|----------|
| `dbfilename` | `dump.rdb` | 保持默认即可。如果一台机器跑多个 Redis 实例,建议用端口号区分,如 `dump-6379.rdb` |
| `dir` | `/var/lib/redis` | 快照文件存放目录。**必须确保该目录有足够磁盘空间**,且 Redis 进程有写权限 |
| `rdbcompression` | `yes` | 开启 LZF 压缩可减少 40%-60% 的磁盘占用,代价是写入时多消耗一点 CPU。**绝大多数场景推荐开启** |
| `rdbchecksum` | `yes` | 开启后在文件末尾写入 CRC64 校验码,加载时会验证完整性。**关闭可略微提升加载速度,但不值得冒险** |
| `rdb-save-incremental-fsync` | `yes` | 开启后,子进程每写入 32MB 就执行一次 `fsync`,避免一次性刷盘造成 I/O 尖峰。对 SSD 尤其友好 |
> [!QUESTION] rdbcompression 关闭能提升多少性能?
> 压缩的 CPU 开销其实很小(LZF 是轻量压缩算法),瓶颈通常在磁盘 I/O 而非 CPU。关闭压缩后文件变大 → 写入时间更长 → 可能反而更慢。所以**除非你有明确的 CPU profiling 数据证明压缩是瓶颈,否则不要关**。
## Fork 过程详解
BGSAVE 的核心操作是 `fork()`——这是 Linux 的系统调用,能以**极低的成本**创建一份进程副本。整个流程如下:
```mermaid
sequenceDiagram
participant Client as 客户端
participant Main as 主进程
participant Child as 子进程
participant Disk as 磁盘
Client->>Main: 发送 BGSAVE 命令
Main->>Main: 创建子进程(fork)
Note over Main: fork 返回 0(子进程视角)<br/>fork 返回 pid(主进程视角)
Main->>Client: 返回 OK(立即响应)
par 主进程继续服务
Main->>Main: 正常处理读写请求
and 子进程执行快照
Child->>Child: 遍历所有 key-value
Child->>Disk: 序列化写入 dump.rdb.tmp
Note over Child,Disk: 写入过程中如果主进程修改了数据<br/>内核通过 COW 机制保护子进程视图
Child->>Main: 通知:快照完成
end
Main->>Disk: rename dump.rdb.tmp -> dump.rdb(原子替换)
Main->>Main: 更新 lastsave 时间戳, 重置 dirty 计数器
```
> [!TIP] 为什么 fork 这么快?
> `fork()` 并不会真的复制整个内存。Linux 使用**"写时复制"(Copy-On-Write, COW)**——fork 之后,父子进程**共享同一份物理内存页**,只在某一方**写入时**才真正拷贝那一页。所以 fork 一个 10GB 的 Redis 实例,实际瞬间开销只是创建页表(通常几十 MB),远小于 10GB。
### 深入理解 COW(Copy-On-Write)
> [!INFO] 图书馆类比
> 想象一个图书馆(物理内存),里面有很多书(内存页)。
> - **fork 之前**:只有主进程一个人在看书
> - **fork 之后**:子进程也进了图书馆,两人**看同一本书**,不需要额外复印——这就是"共享"
> - **主进程想在书上做笔记(写入)**:图书馆员说"不行,这本书要保护",于是**复印了一份**给主进程,子进程继续看原版——这就是"写时复制"
> - **如果主进程不写**:从头到尾只有那一本书,零额外开销
```mermaid
flowchart TD
F["Fork 完成"] --> S["父子进程共享<br/>所有物理内存页"]
S --> W{"主进程收到<br/>写请求?"}
W -->|"否"| R["子进程继续读取原页<br/>(零拷贝,极快)"]
W -->|"是"| C["内核复制该内存页<br/>(Copy-On-Write)"]
C --> N["主进程写入副本页<br/>子进程仍读原页"]
R & N --> D["子进程完成序列化"]
D --> DONE["RDB 文件生成完成"]
```
> [!QUESTION] BGSAVE 期间 Redis 内存会翻倍吗?
> **不会翻倍,但最坏情况接近翻倍。**
>
> fork 本身**不复制任何内存页**。只有当主进程**实际写入**某一页时,那一页才会被 COW 拷贝。两种极端情况:
>
> | 场景 | 额外内存开销 |
> |------|-------------|
> | BGSAVE 期间主进程**几乎不写入** | ≈ 0(只共享,不拷贝) |
> | BGSAVE 期间主进程**全量写入** | ≈ 接近翻倍(每一页都被拷贝) |
> | 实际生产环境 | 通常 **10%-30%**(只有被修改的 key 对应的页才会 COW) |
> [!WARNING] fork 失败的常见原因
> fork 需要内核分配页表空间,如果系统内存不足会失败。关键运维参数:
> - **`vm.overcommit_memory = 1`**:告诉内核"允许过度提交",这是 Redis 官方推荐的 Linux 设置
> - **`vm.overcommit_ratio`**:默认 50,配合 overcommit_memory=2 使用
>
> 如果你看到 `Can't save in background: fork: Cannot allocate memory`,大概率是这个参数没设置对。
### TTL / 过期键处理
> [!QUESTION] RDB 快照会保存已过期的 key 吗?
> **不会。** 这是一个容易误解的点——很多人以为快照是"拍照片",会原封不动地保存内存里所有东西。但实际上,Redis 在"拍照"之前会先"打扫房间"。
具体流程:
1. `BGSAVE` 触发时,主线程**先执行一次主动过期扫描**,把所有已超时的 key 从内存中删除
2. 删除完成后,才 fork 子进程
3. 子进程遍历剩余的 key → 序列化写入 `.rdb` 文件
4. 结论:**`.rdb` 文件中只有有效数据,不包含过期键**
```mermaid
flowchart LR
A["BGSAVE 触发"] --> B["主线程: 主动过期扫描<br/>清理所有已超时 Key"]
B --> C["Fork 子进程"]
C --> D["子进程: 遍历有效数据<br/>序列化写入 .rdb"]
D --> E["RDB 文件中无过期键"]
style E fill:#c4e8b0
```
> [!NOTE] 一个细节:RDB 加载时也会二次检查
> 即使某个 key 在快照时还没过期,但等到恢复加载时已经过期了——Redis 在加载 RDB 文件时会**再次检查 TTL**,过期的 key 不会被加载到内存。这保证了无论什么时候恢复,数据都是"新鲜"的。
## 恢复流程
Redis 启动时会**自动检测** `dir` 目录下的 `.rdb` 文件并加载——你不需要手动执行任何"恢复命令"。
### 场景一:正常重启(最常见)
```bash
# 直接重启即可,Redis 自动加载 dump.rdb
redis-server /path/to/redis.conf
```
Redis 启动日志中你会看到类似:
```
* DB loaded from disk: 0.052 seconds # ← 52ms 加载完成
```
### 场景二:从备份恢复(灾难恢复)
当服务器故障需要从备份 `.rdb` 恢复时:
```bash
# 1. 停止当前 Redis(如果还在运行)
redis-cli SHUTDOWN NOSAVE # NOSAVE = 不再生成新快照,直接关闭
# 2. 把备份的 .rdb 文件放到 Redis 工作目录
cp /backup/dump.rdb /var/lib/redis/dump.rdb
# 3. 启动 Redis,它会自动加载这个 .rdb
redis-server /path/to/redis.conf
# 4. 验证数据恢复
redis-cli DBSIZE # 检查 key 总数
redis-cli INFO keyspace # 查看各 DB 的 key 数量和过期 key 数量
redis-cli GET some_known_key # 抽样验证已知 key 是否存在
```
### 场景三:迁移数据到新实例
```bash
# 在旧实例上手动触发一次快照
redis-cli BGSAVE
# 等待完成
redis-cli LASTSAVE # 观察时间戳变化
# 复制 .rdb 到新服务器
scp /var/lib/redis/dump.rdb user@new-server:/var/lib/redis/
# 在新服务器上启动 Redis(自动加载)
ssh user@new-server "redis-server /path/to/redis.conf"
```
> [!WARNING] 恢复时的常见坑
> 1. **文件权限**:确保 Redis 进程对 `dir` 目录有**读写权限**(启动时需要读取,运行时需要写入新快照)
> 2. **磁盘空间**:`.rdb` 文件恢复时需要临时解压到内存,确保服务器**可用内存 ≥ RDB 文件大小**
> 3. **同时存在 AOF 和 RDB**:如果 `dir` 下同时有 `dump.rdb` 和 `appendonly.aof`,**Redis 优先加载 AOF**(因为 AOF 数据通常更新)。如果你确实想用 RDB 恢复,需要先移走 AOF 文件
> 4. **版本兼容**:低版本 Redis 不能加载高版本生成的 RDB 文件(向后不兼容)
## RDB vs AOF 对比
| 维度 | RDB | AOF | 为什么? |
|------|-----|-----|----------|
| **数据安全性** | ⚠️ 可能丢失两次快照间的数据 | ✅ 最多丢 1 秒数据(everysec 策略) | RDB 是"定期拍照",AOF 是"实时写日记" |
| **恢复速度** | ⚡ 极快(直接加载二进制文件) | 🐢 较慢(逐条回放命令) | 读一本 100 页的书 vs 回放 100 万条操作日志 |
| **文件大小** | ✅ 小(压缩后的二进制) | ❌ 大(文本命令持续追加) | 照片 vs 逐帧视频——信息密度不同 |
| **CPU 开销** | 低(偶尔 fork) | 高(每条写命令都要追加) | 定期批量 vs 持续实时的天然差异 |
| **对写入性能影响** | 极小(fork 子进程异步完成) | 有影响(每次写都要 append) | BGSAVE 不阻塞主线程,AOF fsync 可能短暂阻塞 |
| **适用场景** | 备份、灾难恢复、主从同步 | 对数据安全要求高的在线服务 | 各取所长 |
> [!QUESTION] 生产环境怎么选?
> **答案是:不要二选一,两个都开。** Redis 4.0+ 引入的混合持久化(下文详解)完美解决了这个矛盾——RDB 保证恢复速度,AOF 保证数据安全。
## Redis 混合持久化(推荐)
### 为什么需要混合持久化?
回顾一下两者的痛点:
| 方案 | 优点 | 痛点 |
|------|------|------|
| RDB | 恢复快、文件小 | 两次快照间的数据可能丢失 |
| AOF | 数据几乎不丢 | 恢复慢、文件大 |
> [!QUESTION] 能不能把两者结合起来——用 RDB 加速恢复 + 用 AOF 保证数据安全?
> 可以!Redis 4.0 引入了**混合持久化(Hybrid Persistence)**,正是为了解决这个矛盾。
### 原理:AOF 文件 = RDB 快照 + 增量日志
开启混合持久化后,AOF **重写**时不再只写命令日志,而是:
```mermaid
flowchart LR
A["AOF 重写触发"] --> B["先写入一份 RDB 快照<br/>(覆盖重写时刻的全量数据)"]
B --> C["再追加重写期间的增量命令"]
C --> D["最终 AOF 文件"]
style D fill:#c4e8b0
```
也就是说,生成的 AOF 文件结构变成了:
```
┌──────────────────────────────────────┐
│ RDB 二进制快照(全量数据基线) │ ← 启动时直接加载,秒级完成
├──────────────────────────────────────┤
│ 增量 AOF 命令(快照之后的写操作) │ ← 逐条回放,保证数据不丢
└──────────────────────────────────────┘
```
> [!INFO] 用"写日记"来类比
> - **纯 RDB** = 定期拍照(恢复快,但照片之间的事不知道)
> - **纯 AOF** = 每天写日记(信息完整,但要从头读到尾才能恢复全部记忆)
> - **混合持久化** = 定期拍照 + 每天写日记。恢复时先看最近的照片(RDB 部分),再读照片之后的日记(AOF 部分)——既快又完整
### 如何开启
```conf
# redis.conf
aof-use-rdb-preamble yes # Redis 4.0+, 默认就是 yes
appendonly yes # 必须开启 AOF
```
### 恢复速度对比
```mermaid
flowchart LR
T1["传统 AOF 恢复<br/>回放数百万条命令"] -->|"O(N) 慢"| R1["耗时: 分钟级别"]
H1["RDB 快照加载<br/>秒级还原全量数据"] -->|"O(1) 快"| R2["耗时: 秒级"]
H2["回放少量增量 AOF<br/>仅重写后的命令"] -->|"极少"| R2
style R1 fill:#f9d0c4
style R2 fill:#c4e8b0
```
> [!TIP] 混合持久化几乎是"无脑开启"的配置
> - 恢复速度 ≈ RDB(因为大部分数据在 RDB 部分,AOF 增量部分很少)
> - 数据安全 ≈ AOF(增量日志保证不丢数据)
> - 开销 = AOF rewrite 的 fork 开销(和纯 AOF 一样)
>
> Redis 5.0+ 已默认开启,建议检查你的配置确认一下。
## 最佳实践
> [!SUMMARY] 核心原则:RDB 是备份手段,不是实时数据保障
> 不要把 RDB 当成"实时安全网"——它的定位是**定期快照 + 灾难恢复兜底**。实时数据安全交给 AOF。
1. **不要只依赖 RDB**:生产环境推荐 `AOF + 混合持久化`,AOF 保证数据安全,RDB 加速恢复
2. **合理设置 save 频率**:默认三档适合大部分场景。写入密集型可适当提高频率,但要注意:
- 频率越高 → fork 越频繁 → CPU 和内存压力越大
- 经验法则:`rdb_last_bgsave_time_sec` 超过 **1 秒**就需要关注
3. **备份策略(自动化)**:
```bash
#!/bin/bash
# redis-backup.sh — 每日定时备份脚本示例
REDIS_CLI="redis-cli"
BACKUP_DIR="/backup/redis"
DATE=$(date +%Y%m%d_%H%M%S)
# 1. 触发一次快照
$REDIS_CLI BGSAVE
# 2. 等待完成(轮询 lastsave)
LAST=$($REDIS_CLI LASTSAVE)
while [ "$($REDIS_CLI LASTSAVE)" == "$LAST" ]; do sleep 1; done
# 3. 拷贝并压缩
cp /var/lib/redis/dump.rdb "$BACKUP_DIR/dump-$DATE.rdb"
gzip "$BACKUP_DIR/dump-$DATE.rdb"
# 4. 保留最近 7 天的备份,删除更早的
find "$BACKUP_DIR" -name "dump-*.rdb.gz" -mtime +7 -delete
echo "Backup completed: dump-$DATE.rdb.gz"
```
4. **监控关键指标**:通过 `INFO persistence` 关注以下字段:
| 指标 | 含义 | 告警阈值建议 |
|------|------|-------------|
| `rdb_last_bgsave_status` | 上次 BGSAVE 是否成功 | ≠ `ok` 立即告警 |
| `rdb_last_bgsave_time_sec` | 上次 BGSAVE 耗时 | > 1s 关注,> 5s 告警 |
| `rdb_last_save_time` | 上次成功快照的时间戳 | 距今 > 1h 未触发则排查 |
| `rdb_changes_since_last_save` | 上次快照后的变更 key 数 | 持续增长说明 save 规则可能失效 |
5. **磁盘选择**:RDB 写入对 I/O 有要求,建议使用 SSD 并保持 `rdb-save-incremental-fsync yes`
> [!WARNING] 大 Key 是 RDB 的天敌
> 一个 100MB 的 String key,序列化时子进程写入耗时剧增(单个 key 可能就要写几秒),同时 COW 也会产生大量页面复制。
>
> **排查方法**:`redis-cli --bigkeys` 扫描大 key,发现后优先拆分。详见 [[hhs/Redis/11-运维与性能调优]]。
## 运维速查手册
```bash
# ---- 查看 RDB 状态 ----
INFO persistence
# 重点关注这些字段:
# rdb_last_bgsave_status: ok ← 上次是否成功
# rdb_last_bgsave_time_sec: 2 ← 上次耗时几秒
# rdb_last_save_time: 1715000000 ← 上次成功时间戳(Unix)
# rdb_changes_since_last_save: 1234 ← 自上次快照后的变更 key 数
# ---- 查看快照文件 ----
ls -lh /var/lib/redis/dump.rdb
# -rw-rw---- 1 redis redis 1.2G May 22 03:00 dump.rdb
# ---- 手动触发一次快照并等待完成 ----
redis-cli BGSAVE
# 等待:轮询 LASTSAVE 直到时间戳变化
while [ "$(redis-cli LASTSAVE)" == "$OLD" ]; do sleep 0.5; done
# ---- 检查 RDB 文件是否完整(不启动 Redis 的情况下) ----
# Redis 自带工具,用于检查 .rdb 文件的完整性
redis-check-rdb /var/lib/redis/dump.rdb
# 输出 OK 则文件完整,否则会告诉你哪里损坏了
```
## 关联笔记
- [[hhs/Redis/05-AOF持久化]] — AOF 持久化机制
- [[hhs/Redis/06-主从与哨兵]] — Sentinel 故障转移流程
- [[hhs/Redis/11-运维与性能调优]] — 备份策略与大 Key 治理