28 KiB
tags, create time
| tags | 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 则像记了一本流水账("先加了张三,再删了李四,又改了王五……"),恢复时需要从头到尾把账本念一遍——数据量越大,"念账本"的时间就越长。
触发机制
手动触发
# 当前线程执行,阻塞主线程 ❌ 生产环境禁用!
SAVE
# 主进程 fork 子进程在后台完成(✅ 推荐)
BGSAVE
# 查看上次 BGSAVE 成功的时间戳
LASTSAVE
| 命令 | 行为 | 适用场景 | 风险 |
|---|---|---|---|
SAVE |
同步阻塞,直到快照完成 | 紧急调试,或确认实例无流量时 | 主线程完全卡死——所有客户端请求排队等待 |
BGSAVE |
fork 子进程异步完成 | 生产环境日常备份 | fork 瞬间有极短暂的阻塞(通常毫秒级) |
[!QUESTION] 为什么 SAVE 危险但 Redis 还要提供它? 因为
SAVE可以在 BGSAVE 正在执行时 强制同步保存(BGSAVE 期间再次 BGSAVE 会被拒绝)。这是一个极端场景的"保底"手段。
自动触发——条件持久化
通过 redis.conf 配置多条 save 规则,Redis 用一个**"脏键计数器"(dirty counter)** 来追踪:
save 900 1 # 900 秒内至少有 1 个 key 被修改 → 触发快照
save 300 10 # 300 秒内至少有 10 个 key 被修改
save 60 10000 # 60 秒内至少有 10000 个 key 被修改
[!INFO] 底层是怎么工作的? Redis 内部维护两个计数器:
dirty计数器:自上次 BGSAVE 成功以来,总共被修改的 key 数量(每次写操作 +1)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 分钟的数据损失也更大
本质上是"数据越重要、变更越频繁,保存就越勤"的策略。
# 运行时动态修改(覆盖所有规则)
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 progressSAVE→ 会等待 BGSAVE 完成后再执行这个设计避免了多个子进程争抢 I/O 资源。
RDB 文件结构
可以把 .rdb 文件想象成一个快递包裹:有快递单号(Header)、多个包裹(DB 里的数据)、包裹拆完的标记(EOF)、以及签收校验码(Footer)。
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 不记录"怎么做的",只记录"结果是什么"——就像一张照片远比描述照片的文字更紧凑。
关键配置参数
# ---- 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 的系统调用,能以极低的成本创建一份进程副本。整个流程如下:
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 之后:子进程也进了图书馆,两人看同一本书,不需要额外复印——这就是"共享"
- 主进程想在书上做笔记(写入):图书馆员说"不行,这本书要保护",于是复印了一份给主进程,子进程继续看原版——这就是"写时复制"
- 如果主进程不写:从头到尾只有那一本书,零额外开销
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 通过两道防线确保过期键不会进入最终数据。
第一道防线:子进程遍历时惰性跳过
BGSAVE 触发时,Redis 不会在 fork 前专门做一次"全量过期清理"(这在数据量大时太慢了)。实际流程是:
- 主线程 fork 子进程
- 子进程遍历所有 key,序列化写入
.rdb文件 - 在遍历每个 key 时,子进程会检查 TTL 是否已过期——如果已过期,直接跳过,不写入文件
[!NOTE] 那日常的过期清理呢? Redis 平时通过两种机制清理过期键:
- 惰性删除:访问 key 时发现已过期 → 立即删除
- 定期删除:
serverCron每 100ms 随机抽样一批设置了 TTL 的 key,删除其中已过期的所以到 BGSAVE 触发时,内存中大部分过期键其实已经被日常清理掉了。子进程遍历时再跳过漏网之鱼,双重保障。
第二道防线:加载时二次检查
即使某个 key 在快照时还没过期,但等到恢复加载时已经过期了——Redis 在加载 RDB 文件时会再次检查 TTL,过期的 key 不会被加载到内存。
flowchart LR
A["BGSAVE 触发"] --> B["Fork 子进程"]
B --> C["子进程遍历所有 Key"]
C --> D{"Key 是否<br/>已过期?"}
D -->|"是"| E["跳过,不写入 .rdb"]
D -->|"否"| F["序列化写入 .rdb"]
F --> G["RDB 文件只含有效数据"]
E --> C
style G fill:#c4e8b0
[!QUESTION] 为什么要分两道防线,不能一次性清理干净? 因为时间是流动的。假设一个 key 的 TTL 还剩 1 秒时触发了 BGSAVE,子进程遍历时它还活着(写入了 .rdb),但 2 秒后加载时它已过期。所以加载时的二次检查是必须的——它处理的是"快照时还活着、但恢复时已过期"的时间差。
恢复流程
Redis 启动时会自动检测 dir 目录下的 .rdb 文件并加载——你不需要手动执行任何"恢复命令"。
场景一:正常重启(最常见)
# 直接重启即可,Redis 自动加载 dump.rdb
redis-server /path/to/redis.conf
Redis 启动日志中你会看到类似:
* DB loaded from disk: 0.052 seconds # ← 52ms 加载完成
场景二:从备份恢复(灾难恢复)
当服务器故障需要从备份 .rdb 恢复时:
# 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 是否存在
场景三:迁移数据到新实例
# 在旧实例上手动触发一次快照
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] 恢复时的常见坑
- 文件权限:确保 Redis 进程对
dir目录有读写权限(启动时需要读取,运行时需要写入新快照)- 磁盘空间:
.rdb文件恢复时需要临时解压到内存,确保服务器可用内存 ≥ RDB 文件大小- 同时存在 AOF 和 RDB:如果
dir下同时有dump.rdb和appendonly.aof,Redis 优先加载 AOF(因为 AOF 数据通常更新)。如果你确实想用 RDB 恢复,需要先移走 AOF 文件- 版本兼容:低版本 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 重写时不再只写命令日志,而是:
flowchart LR
A["AOF 重写触发"] --> B["先写入一份 RDB 快照<br/>(覆盖重写时刻的全量数据)"]
B --> C["再追加重写期间的增量命令"]
C --> D["最终 AOF 文件"]
style D fill:#c4e8b0
也就是说,生成的 AOF 文件结构变成了:
┌──────────────────────────────────────┐
│ RDB 二进制快照(全量数据基线) │ ← 启动时直接加载,秒级完成
├──────────────────────────────────────┤
│ 增量 AOF 命令(快照之后的写操作) │ ← 逐条回放,保证数据不丢
└──────────────────────────────────────┘
[!INFO] 用"写日记"来类比
- 纯 RDB = 定期拍照(恢复快,但照片之间的事不知道)
- 纯 AOF = 每天写日记(信息完整,但要从头读到尾才能恢复全部记忆)
- 混合持久化 = 定期拍照 + 每天写日记。恢复时先看最近的照片(RDB 部分),再读照片之后的日记(AOF 部分)——既快又完整
如何开启
# redis.conf
aof-use-rdb-preamble yes # Redis 4.0+, 默认就是 yes
appendonly yes # 必须开启 AOF
恢复速度对比
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。
- 不要只依赖 RDB:生产环境推荐
AOF + 混合持久化,AOF 保证数据安全,RDB 加速恢复 - 合理设置 save 频率:默认三档适合大部分场景。写入密集型可适当提高频率,但要注意:
- 频率越高 → fork 越频繁 → CPU 和内存压力越大
- 经验法则:
rdb_last_bgsave_time_sec超过 1 秒就需要关注
- 备份策略(自动化):
#!/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"
- 监控关键指标:通过
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 规则可能失效 |
- 磁盘选择:RDB 写入对 I/O 有要求,建议使用 SSD 并保持
rdb-save-incremental-fsync yes
[!WARNING] 大 Key 是 RDB 的天敌 一个 100MB 的 String key,序列化时子进程写入耗时剧增(单个 key 可能就要写几秒),同时 COW 也会产生大量页面复制。
排查方法:
redis-cli --bigkeys扫描大 key,发现后优先拆分。详见 hhs/Redis/11-运维与性能调优。
运维速查手册
# ---- 查看 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 则文件完整,否则会告诉你哪里损坏了
常见故障排查
[!WARNING] BGSAVE 失败?按这个顺序排查 以下是最常见的三类故障,按发生概率排序:
1. fork 失败:Can't save in background: fork: Cannot allocate memory
# 检查当前 overcommit 设置
cat /proc/sys/vm/overcommit_memory
# 如果输出 0 或 2 → 需要改为 1
# 临时修改(立即生效,重启失效)
echo 1 > /proc/sys/vm/overcommit_memory
# 永久修改(写入 sysctl)
echo "vm.overcommit_memory = 1" >> /etc/sysctl.conf && sysctl -p
[!INFO] 为什么 overcommit_memory 必须设为 1? fork 需要内核分配页表空间,但此时并不会真正使用等量的物理内存(COW)。
overcommit_memory=0(默认)时,内核会用启发式算法估算——对于大内存 Redis 实例,这个估算可能过于保守而拒绝 fork。设为 1 是告诉内核"不要猜了,直接允许"。
2. 磁盘空间不足:写入 .rdb 时报错
# 检查 dir 目录的可用空间
df -h /var/lib/redis
# 检查实际文件大小
ls -lh /var/lib/redis/dump.rdb
# 紧急处理:临时关闭 RDB 自动触发
redis-cli CONFIG SET save ""
3. RDB 文件损坏:加载时 Short read or OOM loading DB
# 使用 Redis 自带工具检查
redis-check-rdb /var/lib/redis/dump.rdb
# 如果文件损坏且有备份 → 用备份替换
# 如果没有备份 → 看 AOF 是否可用(Redis 优先加载 AOF)
安全性考虑
[!WARNING] RDB 文件是明文存储的!
.rdb文件虽然看起来是"二进制",但其中的 key 名称和字符串类型的 value 都是明文可读的。用strings dump.rdb | head就能看到大部分内容。
生产环境建议:
- 文件权限:确保
.rdb文件权限为600(仅 Redis 用户可读写)chmod 600 /var/lib/redis/dump.rdb - 备份加密:备份到远端存储前,先加密再上传
# 使用 openssl 加密备份 openssl enc -aes-256-cbc -salt -pbkdf2 \ -in dump.rdb -out dump.rdb.enc -pass pass:$REDIS_BACKUP_KEY - 传输加密:跨机器复制时使用
scp或rsync over SSH,避免明文传输 - 敏感数据脱敏:如果存了密码、Token 等敏感信息,建议在应用层加密后再写入 Redis——不要依赖 Redis 自身的"安全性"
[!TIP] Redis 企业版(Redis Enterprise)支持透明的 RDB 文件加密 社区版不支持原生加密。如果你有强合规需求,可以考虑企业版或者在应用层做客户端加密。
关联笔记
- hhs/Redis/05-AOF持久化 — AOF 持久化机制
- hhs/Redis/06-主从与哨兵 — Sentinel 故障转移流程
- hhs/Redis/11-运维与性能调优 — 备份策略与大 Key 治理