--- tags: [Redis, 缓存, 持久化, RDB] create time: 2026-05-15 18:12 --- # RDB 持久化 ## 概述 > [!SUMMARY] RDB 是什么? > RDB(Redis Database)是 Redis 最早的持久化机制,通过在特定时间点将内存中的数据集写入磁盘形成**快照文件(.rdb)**,实现数据持久化。 ### 核心特点 | 特性 | 说明 | |------|------| | ⚡ 恢复极快 | 二进制文件直接加载,避免命令回放 | | 📦 文件紧凑 | 压缩存储,远小于等量 AOF 日志 | | 🔧 架构简单 | 无额外复杂逻辑,适合备份和迁移 | | ⚠️ 数据窗口丢失 | 两次快照之间的写操作不可恢复 | ### 何时使用 - **大数据集冷备**:定期全量备份到 S3 / OSS - **灾难恢复**:配合 CI/CD 快速重建实例 - **大规模数据场景**:恢复速度优先于毫秒级数据一致性 > [!QUESTION] 为什么 RDB 恢复比 AOF 快? > RDB 是直接加载二进制快照到内存,AOF 则需要逐条回放命令——数据量越大差距越明显。 ## 触发机制 ### 手动触发 ```bash # 当前线程执行,阻塞主线程 ❌ SAVE # 主进程 fork 子进程在后台完成(推荐) BGSAVE # 查看上次 BGSAVE 状态 LASTSAVE ``` | 命令 | 行为 | 影响 | |------|------|------| | `SAVE` | 同步保存,主线程阻塞 | **生产环境禁用!** | | `BGSAVE` | fork 子进程异步保存 | 主线程继续服务,但触发 fork 瞬间短暂停顿 | | `BGREWRITEAOF` | AOF rewrite | 与 BGSAVE 可同时运行(不同子进程) | ### 自动触发——条件持久化 通过 `redis.conf` 配置: ```conf save 900 1 # 900 秒内至少有 1 个 key 被修改 → 触发快照 save 300 10 # 300 秒内至少有 10 个 key 被修改 save 60 10000 # 60 秒内至少有 10000 个 key 被修改 ``` > [!QUESTION] 为什么要三档条件? > - **低活跃度**:900s/1key 保证最终一致 > - **中活跃度**:300s/10key 平衡频率和数据量 > - **高活跃度**:60s/10000key 高频大量变更时快速落盘 ```bash # 运行时动态修改 CONFIG SET save "900 1\n300 10\n60 10000" # 临时关闭自动快照(谨慎!) CONFIG SET save "" ``` > [!WARNING] CONFIG SET 不持久 > `CONFIG SET` 仅在内存中生效,重启后失效。修改 `redis.conf` 并 reload 才是持久化的方式。 ### 其他触发场景 | 场景 | 说明 | |------|------| | **shutdown 保存** | `SHUTDOWN` / `SHUTDOWN NOSAVE` 会在关闭前执行一次完整 RDB 保存(除非显式传 NOSAVE) | | **AOF rewrite** | `BGREWRITEAOF` 重写 AOF 时同样会 fork 子进程生成 RDB preamble(开启混合持久化时) | | **主从同步** | 新从节点首次全量同步时,主节点会自动触发 BGSAVE 并将快照传给从节点 | > [!TIP] 避免频繁触发 > 多个触发条件同时满足时,Redis 会去重,仅执行一次 BGSAVE。若已有 BGSAVE 在执行,后续的 SAVE 请求将被忽略。 ## RDB 文件结构 Redis 7.0+ 引入了新格式,更紧凑且支持增量复制: ```mermaid flowchart TD H1["Header"] --> H2["Magic: REDIS"] H1 --> H3["RDB Version"] H1 --> H4["Reserved / CRC64"] D1["DB Number
1 byte"] --> D2["Key Type
1 byte"] D2 --> D3["Encoded Key + Value"] E1["EOF Marker"] --> E2["CRC64 Checksum
8 bytes"] H1 & H2 & H3 & H4 ==> "Dataset Section" D1 & D2 & D3 ==> "Dataset Section" D3 -.-> "... N keys ..." E1 & E2 ==> "Footer" ``` ### 各段含义 | 段落 | 内容 | 说明 | |------|------|------| | **Header** | Magic + Version + 校验 | `REDIS` 魔数标识文件类型;版本号用于兼容性判断;CRC64 用于完整性校验 | | **DB Selector** | 数据库编号 | 默认 16 个 DB(0-15),只序列化非空 DB | | **Key-Value Pairs** | 类型 + 编码 + 数据 | 每个 key 携带类型标记(String/List/Hash/ZSet…),底层编码直接序列化,因此文件极紧凑 | | **EOF** | `0xFF` 终止符 | 标识数据段结束 | | **Footer** | CRC64 校验和 | 8 字节,启动加载时校验文件完整性 | > [!QUESTION] 为什么 RDB 文件比 AOF 小很多? > 因为 RDB 直接序列化底层数据结构(如 ziplist、intset),而非记录产生这些数据的命令字符串。一个 Hash 用 ziplist 存储可能只有几十字节,但生成它的 HSET 命令可能有数百字节。 ## 关键配置参数 ```conf # ---- RDB 核心参数 ---- dbfilename dump.rdb # 快照文件名 dir /var/lib/redis # 工作目录,RDB/AOF 文件存放位置 rdbcompression yes # 是否使用 LZF 压缩字符串(推荐开启,CPU vs 磁盘权衡) rdbchecksum yes # 是否在文件末尾写入 CRC64 校验(推荐开启) rdb-save-incremental-fsync yes # 写入磁盘时增量 fsync,避免一次性大量 I/O 阻塞 rdb-del-sync-files no # 无盘复制时,是否删除用于同步的临时 RDB(Redis 7+) ``` > [!TIP] rdbcompression 的取舍 > 开启压缩可以显著减少磁盘占用(通常压缩 40%-60%),代价是写入时多消耗一点 CPU。对于绝大多数场景,**保持 yes** 是最优解。只有在 CPU 极度敏感、磁盘充裕的特殊场景下才考虑关闭。 ## Fork 过程详解 ```mermaid sequenceDiagram participant Server as Redis Server participant Parent as 主进程 child ForkGroup as Forked Child participant Child as 写时复制子进程 participant Disk as 磁盘 (.rdb) end Server->>Parent: BGSAVE 指令 Parent->>Server: OK(立即返回) Parent->>Child: fork() 创建子进程 Note over Child: 子进程独享父进程
内存副本(写时复制 COW) Child->>Disk: 遍历所有 key → 序列化 Note over Child: 主进程仍可读写
COW 机制按需拷贝脏页 Child->>Parent: 生成临时 .rdb.tmp Parent->>Disk: RENAME 原子替换 Parent->>Server: BGSAVE 完成通知 ``` > [!WARNING] COW 内存消耗 > fork 后主进程若持续写入大值,会导致 COW 副本膨胀。例如你有一个 1GB 的值被频繁修改,fork 后每次写都可能多拷贝一页(4KB)。高峰期建议关闭自动 RDB,仅依赖 AOF。 ### 深入理解 COW(Copy-On-Write) fork 后父子进程共享同一份物理内存页(只读),只有当某一方**写入**时,内核才会真正复制该页——这就是"写时复制"。 ```mermaid flowchart TD F["Fork 完成"] --> S["父子进程共享
所有物理内存页"] S --> W{"主进程收到
写请求?"} W -->|"否"| R["子进程继续读取
(零拷贝, 极快)"] W -->|"是"| C["内核复制该内存页
(Copy-On-Write)"] C --> N["主进程写入副本
子进程仍读原页"] R & N --> D["子进程完成序列化"] D --> DONE["RDB 文件生成"] ``` > [!QUESTION] 为什么 Redis 占用内存会翻倍? > 这是面试高频题。答案是:**不会翻倍,但最坏情况接近翻倍。** fork 本身不复制内存,只有**主进程实际写入的页面**才会被 COW。如果 BGSAVE 期间主进程几乎不写入,额外内存开销几乎为零。但若全量写入,就会逐步逼近 2 倍。 > > **运维经验**:保持系统 `vm.overcommit_memory=1`(Linux 默认),否则 fork 可能因内核认为内存不足而失败。 ### TTL / 过期键处理 > [!QUESTION] RDB 快照会保存已过期的 key 吗? > **不会。** Redis 在 fork 子进程前会先执行一次主动过期扫描,清理掉所有已超时的 key。 具体流程: 1. `BGSAVE` 触发时,主线程先将当前所有已过期 key 从内存中删除 2. 子进程 fork 完成后遍历剩余 key → 序列化到 .rdb 文件 3. 因此:**RDB 文件中不会出现已过期键** ```mermaid flowchart LR A["BGSAVE 触发"] --> B["主线程: 主动过期
清理超时 Key"] B --> C["Fork 子进程"] C --> D["子进程: 遍历剩余数据
序列化到 .rdb"] D --> E[".rdb 无过期键"] style E fill:"#c4e8b0" ``` > [!NOTE] 过期策略差异 > 虽然 RDB 不保存过期键,但 AOF 可能包含过期命令(如之前的 SET k v EX 100)。AOF 重写时会主动过滤过期键,恢复时也只会加载有效数据。两者优先保证启动时的数据一致性。 ## 恢复流程 ```bash # 1. 停止 Redis redis-cli SHUTDOWN NOSAVE # 2. 把 .rdb 放到 redis_dir(由 dir 配置决定) cp dump.rdb /var/lib/redis/ # 3. 启动 Redis redis-server --daemonize yes # 4. 验证数据恢复 redis-cli DBSIZE redis-cli KEYS "*" ``` ## RDB vs AOF 对比 | 维度 | RDB | AOF | |------|-----|-----| | 数据安全性 | 两次快照间数据丢失 | 每秒/每次可配置,接近零丢失 | | 恢复速度 | ⚡ 极快(直接加载二进制文件) | 🐢 较慢(需要回放日志) | | 文件大小 | ✅ 小(紧凑压缩) | ❌ 大(文本命令日志) | | CPU 开销 | 低(偶尔 fork) | 高(持续追加写入) | | 适用场景 | 备份、灾难恢复、大数据集冷备 | 对可用性要求高的在线服务 | | 重启耗时 | 秒级 | 取决于日志长度(可 pre-fork) | ## Redis 7 混合持久化 结合 RDB + AOF 的优点: ```conf aof-use-rdb-preamble yes ``` 开启后,AOF rewrite 不再只追加命令日志,而是先写入一份 RDB 快照,再追加后续命令: ```mermaid flowchart LR T1["传统 AOF 重建
逐条命令回放"] -->|"O(N) 慢"| R1["重建耗时: 长"] H1["RDB 二进制快照
全量快速还原"] -->|"秒级加载"| R2["重建耗时: 短"] H2["AOF 增量日志
仅保留变更后操作"] -->|"精确到毫秒"| R2 style R1 fill:"#f9d0c4" style R2 fill:"#c4e8b0" ``` > [!TIP] 混合持久化几乎是无脑开启的配置——启动快 + 不丢数据。 ## 最佳实践 > [!SUMMARY] 核心原则:RDB 是备份手段,不是实时数据保障 1. **不要只依赖 RDB**:生产环境推荐 `AOF + RDB` 混合持久化,AOF 保证数据安全性,RDB 保证快速恢复 2. **合理设置触发频率**:默认三档(900s/300s/60s)适合大部分场景;写入密集型业务可适当提高频率,但注意 fork 开销 3. **监控 fork 耗时**:`INFO persistence` 中的 `rdb_last_bgsave_time_sec`,超过 1s 就需要关注 4. **备份策略**:定期将 `.rdb` 文件拷贝到异地存储(S3/OSS),建议保留多个历史版本 5. **磁盘选择**:RDB 文件写入对磁盘 I/O 有要求,建议使用 SSD 并配合 `rdb-save-incremental-fsync` > [!WARNING] 大 Key 是 RDB 的天敌 > 一个 100MB 的 String key,序列化时会导致子进程写入耗时剧增,同时 COW 也可能产生大量页面复制。发现大 Key 后应优先拆分。 ## 运维要点 ```bash # 监控 RDB 相关指标 INFO persistence # rdb_last_bgsave_status: OK # rdb_last_save_time: 1715000000 ← 最后成功时间戳 # 检查文件大小 ls -lh /var/lib/redis/dump.rdb # 压缩备份(已内置 LZ4 压缩,通常不需要额外压缩) tar czf redis-backup.tar.gz dump.rdb ``` ## 关联笔记 - [[hhs/Redis/05-AOF持久化]] — AOF 持久化机制 - [[hhs/Redis/06-主从与哨兵]] — Sentinel 故障转移流程 - [[hhs/Redis/11-运维与性能调优]] — 备份策略与大 Key 治理