考察点: fork子进程、写时复制、AOF重写、混合持久化
🎬 开场:一个关于"记忆"的故事
想象你正在玩一个超级好玩的游戏 🎮:
场景1(没有存档):
你:哇,终于打到第100关了!太爽了!
电脑:(突然断电)💥
你:(重启后)什么?!又回到第1关?!😭
场景2(有存档):
你:打到第100关,存个档先~
电脑:(突然断电)💥
你:(重启后加载存档)太好了,继续从第100关开始!😎
Redis的持久化,就是给内存数据"存档"!
Redis是内存数据库,数据都在内存中(超快⚡),但断电就丢失。持久化就是把内存数据保存到硬盘,确保数据不丢失。
第一部分:为什么需要持久化? 🤔
1.1 Redis的特点
Redis = Remote Dictionary Server(远程字典服务器)
核心特点:
✅ 数据存储在内存中 → 速度超快(us级别)
❌ 断电/重启数据丢失 → 需要持久化
1.2 持久化的作用
| 场景 | 没有持久化 | 有持久化 |
|---|---|---|
| 服务器重启 | 数据全丢 | 从硬盘恢复 |
| 进程崩溃 | 数据全丢 | 从硬盘恢复 |
| 灾难恢复 | 无法恢复 | 备份文件恢复 |
| 数据迁移 | 无法迁移 | 拷贝文件即可 |
1.3 生活比喻 📝
Redis持久化 = 学生做笔记
| 类型 | 比喻 | 特点 |
|---|---|---|
| 不持久化 | 上课只听不记,考试全靠记忆 | 速度快,但容易忘 |
| RDB | 每天下课拍一张黑板照片 | 快照完整,但丢失当天数据 |
| AOF | 老师说一句,记一句 | 记录详细,但笔记太多 |
| 混合持久化 | 拍照片+记重点 | 完美结合 |
第二部分:RDB持久化(快照) 📸
2.1 什么是RDB?
RDB(Redis Database) = 在某个时间点,把内存中的全部数据拍个"快照",保存到硬盘。
时刻1:内存数据
+------------------+
| key1: "value1" |
| key2: 123 |
| key3: [1,2,3] | ===拍照====> dump.rdb
| key4: {a:1,b:2} | (二进制文件)
+------------------+
2.2 RDB的触发方式
方式1:手动触发
# 方式1:SAVE命令(同步,阻塞)
127.0.0.1:6379> SAVE
OK # 执行期间Redis被阻塞,无法处理其他命令
# 方式2:BGSAVE命令(异步,推荐)
127.0.0.1:6379> BGSAVE
Background saving started # 后台执行,不阻塞
SAVE vs BGSAVE:
SAVE(同步):
Redis主进程 → 直接写RDB文件 → 阻塞所有客户端
↓
⏰ 数据量大时可能阻塞几分钟!
BGSAVE(异步):
Redis主进程 → fork子进程 → 子进程写RDB文件
↓ ↓
继续处理请求 在后台慢慢写
方式2:自动触发(配置文件)
# redis.conf 配置
save 900 1 # 900秒内至少1次修改,触发BGSAVE
save 300 10 # 300秒内至少10次修改,触发BGSAVE
save 60 10000 # 60秒内至少10000次修改,触发BGSAVE
# 解读:
# "900秒内改了1次数据,太久了,该存档了!"
# "300秒内改了10次数据,变化挺多,存档!"
# "60秒内改了10000次,变化太快了,赶紧存档!"
实际例子:
# 假设配置:save 60 5
时间轴:
00:00 - 启动Redis,内存:key1=1
00:10 - SET key2 2 (修改1次)
00:20 - SET key3 3 (修改2次)
00:30 - SET key4 4 (修改3次)
00:40 - SET key5 5 (修改4次)
00:50 - SET key6 6 (修改5次) → 触发BGSAVE!
00:50 - 开始后台保存快照...
00:52 - dump.rdb 写入完成
方式3:shutdown时自动保存
127.0.0.1:6379> SHUTDOWN SAVE
# 关闭前自动执行SAVE
127.0.0.1:6379> SHUTDOWN NOSAVE
# 关闭前不保存(数据丢失)
2.3 RDB的实现原理(重要!)⭐⭐⭐⭐⭐
核心技术:fork() + 写时复制(Copy-On-Write, COW)
1️⃣ Redis主进程执行BGSAVE
2️⃣ 调用fork()创建子进程
3️⃣ 子进程共享主进程的内存数据(不复制!)
4️⃣ 子进程遍历内存,将数据写入临时RDB文件
5️⃣ 写完后,用临时文件替换旧的dump.rdb
6️⃣ 子进程退出
图解:
主进程(Redis服务)
|
├─> fork() ────> 子进程(生成RDB)
| |
↓ ↓
处理客户端请求 遍历内存,写RDB文件
| |
↓ ↓
继续运行 dump.rdb 完成后退出
写时复制(COW)原理:
初始状态:
主进程内存:[key1, key2, key3, key4]
↑ 子进程共享(不复制内存)
客户端修改:SET key2 new_value
主进程内存:[key1, key2_new, key3, key4]
↑ 主进程复制key2,修改副本
子进程视角:[key1, key2_old, key3, key4]
↑ 子进程看到的还是旧值
结果:
- 子进程保存的是fork时刻的快照
- 主进程可以继续修改数据
- 不会互相影响
关键点:
- ✅ fork速度快:复制页表,不复制数据(利用操作系统的COW机制)
- ✅ 不阻塞主进程:子进程在后台工作
- ⚠️ 内存占用:如果数据修改频繁,内存占用会翻倍
2.4 RDB文件格式
dump.rdb 文件结构:
+----------------+
| REDIS 头部 | (魔数,版本号)
+----------------+
| 数据库选择 | (SELECT 0)
+----------------+
| 键值对数据 | (key-value pairs)
| - 类型 |
| - 过期时间 |
| - key |
| - value |
+----------------+
| EOF 标记 | (文件结束)
+----------------+
| 校验和 | (CRC64校验)
+----------------+
2.5 RDB的优缺点
✅ 优点:
-
文件紧凑:二进制格式,体积小
内存100MB数据 → RDB文件可能只有50MB -
恢复速度快:直接加载到内存
恢复1GB数据大约需要几秒钟 -
适合备份:定时生成快照,易于备份和恢复
# 每天备份 0 3 * * * cp /var/lib/redis/dump.rdb /backup/redis-$(date +%Y%m%d).rdb -
不影响性能:子进程处理,主进程继续服务
❌ 缺点:
-
数据丢失:两次快照之间的数据可能丢失
00:00 - 生成快照1(保存了1000条数据) 00:10 - 写入500条新数据 00:15 - 服务器崩溃 💥 结果:这500条数据丢失! -
fork耗时:数据量大时,fork可能阻塞几百毫秒
10GB数据,fork可能需要200-500ms -
CPU占用:子进程压缩、序列化数据消耗CPU
第三部分:AOF持久化(日志) 📝
3.1 什么是AOF?
AOF(Append Only File) = 把Redis执行的每一条写命令记录下来。
客户端执行:
SET key1 "value1"
SET key2 123
LPUSH list1 "a" "b" "c"
DEL key1
AOF文件记录:
*3 # 3个参数
$3 # 第1个参数长度3
SET
$4
key1
$6
value1
*3
$3
SET
$4
key2
$3
123
...
3.2 AOF的三种写入策略
# redis.conf 配置
appendfsync always # 每条命令都写入(最安全,最慢)
appendfsync everysec # 每秒写入一次(推荐)
appendfsync no # 由操作系统决定(最快,最不安全)
策略对比:
| 策略 | 安全性 | 性能 | 数据丢失 |
|---|---|---|---|
| always | 最高 | 最慢(1000 TPS) | 最多丢1条命令 |
| everysec | 较高 | 较快(10万+ TPS) | 最多丢1秒数据 |
| no | 最低 | 最快 | 可能丢失几分钟数据 |
图解:
always模式:
客户端 → SET key1 "value1" → 写入AOF缓冲区 → 立即fsync到磁盘
↓
慢但安全
everysec模式(推荐):
客户端 → SET key1 "value1" → 写入AOF缓冲区 → 每秒fsync一次
→ SET key2 "value2" → ↓
→ SET key3 "value3" → 缓冲区
→ ... ↓
后台线程每秒写入磁盘
no模式:
客户端 → 写命令 → AOF缓冲区 → 操作系统决定啥时候写入
↓
不可控
3.3 AOF重写(压缩日志)⭐⭐⭐⭐
问题:AOF文件会越来越大!
原始命令:
SET key1 "a"
SET key1 "b"
SET key1 "c"
SET key1 "d"
SET key1 "e"
RPUSH list1 "item1"
RPUSH list1 "item2"
RPUSH list1 "item3"
...
RPUSH list1 "item1000"
结果:AOF文件10GB!😱
解决:AOF重写(Rewrite)
重写后:
SET key1 "e" # 只保留最终结果
RPUSH list1 "item1" "item2" ... "item1000" # 合并成一条
结果:AOF文件只有10MB!😎
触发方式:
# 方式1:手动触发
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
# 方式2:自动触发(配置文件)
auto-aof-rewrite-percentage 100 # 增长100%触发
auto-aof-rewrite-min-size 64mb # 最小64MB才触发
# 解读:
# AOF文件大于64MB,且比上次重写后大了100%,就触发重写
示例:
初始大小:64MB
写入后:128MB(增长100%) → 触发重写 → 压缩到70MB
再写入:140MB(增长100%) → 触发重写 → 压缩到80MB
AOF重写的实现原理:
1️⃣ Redis主进程fork子进程
2️⃣ 子进程读取当前内存数据,生成新的AOF文件
3️⃣ 主进程继续处理命令,同时:
- 写入旧AOF文件
- 写入AOF重写缓冲区
4️⃣ 子进程完成后,通知主进程
5️⃣ 主进程将重写缓冲区的增量数据追加到新AOF
6️⃣ 用新AOF替换旧AOF
图解:
主进程 子进程
|
├─ fork() ──────────────────> 子进程
| |
↓ ↓
处理客户端请求 读取内存数据
| |
├─> 写入旧AOF ↓
├─> 写入重写缓冲区 生成新AOF文件
| |
↓ ↓
继续运行 完成,通知主进程
| ↓
├─ 追加重写缓冲区到新AOF 退出
├─ 用新AOF替换旧AOF
↓
完成
3.4 AOF的优缺点
✅ 优点:
- 数据安全:最多丢失1秒数据(everysec模式)
- 可读性好:纯文本格式,可以手动修复
# 查看AOF文件 tail -f appendonly.aof # 修复损坏的AOF redis-check-aof --fix appendonly.aof - 自动重写:防止文件无限增长
❌ 缺点:
-
文件更大:比RDB大很多
相同数据: RDB文件:50MB AOF文件:200MB(4倍) -
恢复速度慢:需要重放所有命令
恢复1GB数据: RDB:几秒钟 AOF:几分钟 -
性能影响:always模式会严重影响性能
第四部分:混合持久化(4.0+推荐)⭐⭐⭐⭐⭐
4.1 什么是混合持久化?
Redis 4.0+ 推出的新特性,结合RDB和AOF的优点。
# 开启混合持久化
aof-use-rdb-preamble yes # 默认yes
4.2 混合持久化的原理
AOF重写时:
旧方式(纯AOF):
[SET key1 "a"]
[SET key2 "b"]
[SET key3 "c"]
...(全是命令)
新方式(混合):
┌─────────────┐
│ RDB格式数据 │ ← 当前内存快照(二进制)
├─────────────┤
│ AOF格式命令 │ ← 重写期间的新命令(文本)
└─────────────┘
优势:
✅ 恢复速度快:大部分数据用RDB快速加载
✅ 数据安全:增量数据用AOF保证
✅ 文件相对小:RDB部分很紧凑
4.3 恢复过程
Redis启动 → 检查AOF文件
↓
[ RDB部分 ] → 快速加载(90%的数据)
↓
[ AOF部分 ] → 重放命令(10%的增量)
↓
恢复完成!
第五部分:RDB vs AOF vs 混合持久化 🥊
5.1 全面对比表
| 维度 | RDB | AOF | 混合持久化 |
|---|---|---|---|
| 数据完整性 | 较差(丢失较多) | 好(最多丢1秒) | 好(最多丢1秒) |
| 文件大小 | 小(压缩) | 大(4-5倍) | 中等(2-3倍) |
| 恢复速度 | 快(秒级) | 慢(分钟级) | 快(略慢于RDB) |
| 对性能影响 | 小(fork时短暂影响) | 中等(everysec) | 中等 |
| 可读性 | 无(二进制) | 好(文本) | RDB部分不可读 |
| 实现复杂度 | 简单 | 中等 | 中等 |
| 版本 | 所有版本 | 所有版本 | 4.0+ |
5.2 场景选择
场景1:缓存场景(数据可丢失)
推荐:RDB 或 不持久化
理由:
- 数据可以从MySQL重建
- 追求性能,不在意数据丢失
- 节省磁盘空间
配置:
save 900 1
save 300 10
save 60 10000
appendonly no
场景2:数据重要(不能丢)
推荐:混合持久化(AOF + RDB)
理由:
- 数据安全优先
- 恢复速度也要保证
- 最佳平衡方案
配置:
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
场景3:对性能极度敏感
推荐:RDB + 主从复制
理由:
- RDB对性能影响最小
- 主从复制保证高可用
- 从库可以做持久化
配置:
# 主库
save "" # 关闭RDB
appendonly no
# 从库
save 900 1
appendonly yes
场景4:数据量特别大
推荐:RDB
理由:
- AOF文件太大,恢复太慢
- RDB压缩效果好
配置:
save 900 1
save 300 10
appendonly no
5.3 生产环境推荐配置 🏆
# redis.conf - 生产环境推荐配置
# 1. 开启AOF
appendonly yes
# 2. 每秒同步(平衡性能和安全)
appendfsync everysec
# 3. 开启混合持久化
aof-use-rdb-preamble yes
# 4. 自动AOF重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 5. RDB作为辅助
save 900 1
save 300 10
save 60 10000
# 6. 文件名
dbfilename dump.rdb
appendfilename "appendonly.aof"
# 7. 目录
dir /var/lib/redis
# 8. 重写期间允许写入
no-appendfsync-on-rewrite no
第六部分:实战操作 💻
6.1 检查持久化状态
# 连接Redis
redis-cli
# 查看RDB信息
127.0.0.1:6379> INFO persistence
# 输出:
# rdb_changes_since_last_save:1234 # 上次保存后的修改次数
# rdb_last_save_time:1698288000 # 上次保存时间戳
# rdb_last_bgsave_status:ok # 上次BGSAVE状态
# rdb_last_bgsave_time_sec:2 # 上次BGSAVE耗时
# aof_enabled:1 # AOF是否开启
# aof_rewrite_in_progress:0 # 是否正在重写
# aof_current_size:12345678 # 当前AOF大小
# aof_base_size:10000000 # 上次重写后的大小
6.2 手动触发持久化
# 触发RDB(阻塞)
SAVE
# 触发RDB(后台)
BGSAVE
# 触发AOF重写
BGREWRITEAOF
# 查看进度
INFO persistence
6.3 数据恢复演练
# 1. 停止Redis
redis-cli SHUTDOWN SAVE
# 2. 备份文件
cp /var/lib/redis/dump.rdb /backup/
cp /var/lib/redis/appendonly.aof /backup/
# 3. 模拟数据丢失
rm /var/lib/redis/*
# 4. 恢复数据
cp /backup/dump.rdb /var/lib/redis/
cp /backup/appendonly.aof /var/lib/redis/
# 5. 启动Redis
redis-server /etc/redis/redis.conf
# 6. 验证数据
redis-cli
127.0.0.1:6379> KEYS *
6.4 监控持久化性能
#!/usr/bin/env python3
import redis
import time
r = redis.Redis(host='localhost', port=6379)
while True:
info = r.info('persistence')
print(f"""
RDB状态:
- 上次保存后修改: {info['rdb_changes_since_last_save']}
- 上次BGSAVE状态: {info['rdb_last_bgsave_status']}
- 上次BGSAVE耗时: {info['rdb_last_bgsave_time_sec']}秒
AOF状态:
- AOF开启: {info['aof_enabled']}
- AOF当前大小: {info.get('aof_current_size', 0) / 1024 / 1024:.2f}MB
- 正在重写: {info.get('aof_rewrite_in_progress', 0)}
""")
time.sleep(10)
第七部分:常见问题与优化 ⚠️
7.1 fork耗时过长
问题:
数据量10GB,fork可能阻塞500ms
解决方案:
- 使用更快的硬件:增加内存,使用SSD
- 降低Redis实例大小:拆分成多个实例
- 关闭透明大页:
echo never > /sys/kernel/mm/transparent_hugepage/enabled - 监控fork耗时:
127.0.0.1:6379> INFO stats | grep fork latest_fork_usec:2500 # 2.5ms
7.2 内存占用翻倍
问题:
持久化期间,内存占用翻倍(写时复制)
解决方案:
- 预留足够内存:实际内存应为数据量的2倍
- 降低写操作频率:避免持久化时大量写入
- 错峰持久化:在低峰期执行
7.3 AOF文件损坏
问题:
Redis宕机时,AOF文件可能不完整
解决方案:
# 检查AOF文件
redis-check-aof appendonly.aof
# 修复AOF文件
redis-check-aof --fix appendonly.aof
# 输出:
# AOF analyzed: size=12345678, ok_up_to=12345000, diff=678
# This will shrink the AOF from 12345678 bytes, with 678 bytes, to 12345000 bytes
# Continue? [y/N]: y
# Successfully truncated AOF
7.4 磁盘空间不足
问题:
AOF重写时需要双倍磁盘空间
解决方案:
-
定期清理:
# 删除旧备份 find /backup -name "*.rdb" -mtime +7 -delete -
压缩备份:
gzip /backup/dump.rdb -
监控磁盘:
df -h /var/lib/redis
🎓 总结:持久化决策树
[你的数据重要吗?]
/ \
不重要 重要
↓ ↓
[不持久化] [需要快速恢复吗?]
或 RDB / \
重要 不重要
↓ ↓
[混合持久化] [纯AOF]
(推荐90%场景) (最安全)
记忆口诀 🎵
RDB好比拍照片,
定时保存一瞬间。
文件小来恢复快,
两次快照数据险。
AOF犹如流水账,
命令一条条记详。
数据安全丢失少,
文件大来恢复长。
混合持久是首选,
快照日志两结合。
恢复速度数据全,
生产环境它最佳!
最佳实践清单 ✅
☑ 开启AOF(appendonly yes)
☑ 使用everysec策略(平衡性能和安全)
☑ 开启混合持久化(aof-use-rdb-preamble yes)
☑ 配置自动AOF重写(防止文件过大)
☑ 定期备份RDB文件(异地备份)
☑ 监控fork耗时和内存占用
☑ 主从分离持久化(主库关闭,从库开启)
☑ 使用SSD提高IO性能
☑ 测试数据恢复流程
☑ 设置磁盘空间告警
📚 面试要点
- RDB原理:fork子进程,写时复制,快照式持久化
- AOF原理:记录写命令,三种fsync策略
- AOF重写:压缩日志,fork子进程,重写缓冲区
- 混合持久化:RDB+AOF,兼顾速度和安全
- 选择依据:数据重要性、恢复速度、性能要求
- 生产配置:混合持久化 + everysec + 主从分离
最后总结:
持久化就像给数据上保险 🛡️:
- RDB = 短期意外险(快但保障有限)
- AOF = 长期全保险(安全但成本高)
- 混合持久化 = 组合险(最佳选择)
记住:没有持久化的Redis,就像走钢丝不系安全绳! ⚠️
加油,Redis运维工程师!💪