💾 Redis持久化:给内存数据上个"保险"

22 阅读13分钟

考察点: 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的优缺点

✅ 优点:

  1. 文件紧凑:二进制格式,体积小

    内存100MB数据 → RDB文件可能只有50MB
    
  2. 恢复速度快:直接加载到内存

    恢复1GB数据大约需要几秒钟
    
  3. 适合备份:定时生成快照,易于备份和恢复

    # 每天备份
    0 3 * * * cp /var/lib/redis/dump.rdb /backup/redis-$(date +%Y%m%d).rdb
    
  4. 不影响性能:子进程处理,主进程继续服务

❌ 缺点:

  1. 数据丢失:两次快照之间的数据可能丢失

    00:00 - 生成快照1(保存了1000条数据)
    00:10 - 写入500条新数据
    00:15 - 服务器崩溃 💥
    结果:这500条数据丢失!
    
  2. fork耗时:数据量大时,fork可能阻塞几百毫秒

    10GB数据,fork可能需要200-500ms
    
  3. 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. 数据安全:最多丢失1秒数据(everysec模式)
  2. 可读性好:纯文本格式,可以手动修复
    # 查看AOF文件
    tail -f appendonly.aof
    
    # 修复损坏的AOF
    redis-check-aof --fix appendonly.aof
    
  3. 自动重写:防止文件无限增长

❌ 缺点:

  1. 文件更大:比RDB大很多

    相同数据:
    RDB文件:50MB
    AOF文件:200MB(4倍)
    
  2. 恢复速度慢:需要重放所有命令

    恢复1GB数据:
    RDB:几秒钟
    AOF:几分钟
    
  3. 性能影响: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 全面对比表

维度RDBAOF混合持久化
数据完整性较差(丢失较多)好(最多丢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

解决方案:

  1. 使用更快的硬件:增加内存,使用SSD
  2. 降低Redis实例大小:拆分成多个实例
  3. 关闭透明大页
    echo never > /sys/kernel/mm/transparent_hugepage/enabled
    
  4. 监控fork耗时
    127.0.0.1:6379> INFO stats | grep fork
    latest_fork_usec:2500  # 2.5ms
    

7.2 内存占用翻倍

问题:

持久化期间,内存占用翻倍(写时复制)

解决方案:

  1. 预留足够内存:实际内存应为数据量的2倍
  2. 降低写操作频率:避免持久化时大量写入
  3. 错峰持久化:在低峰期执行

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重写时需要双倍磁盘空间

解决方案:

  1. 定期清理

    # 删除旧备份
    find /backup -name "*.rdb" -mtime +7 -delete
    
  2. 压缩备份

    gzip /backup/dump.rdb
    
  3. 监控磁盘

    df -h /var/lib/redis
    

🎓 总结:持久化决策树

          [你的数据重要吗?]
            /          \
        不重要          重要
          ↓              ↓
     [不持久化]      [需要快速恢复吗?]
      或 RDB         /            \
                  重要          不重要
                   ↓              ↓
            [混合持久化]      [纯AOF]
         (推荐90%场景)    (最安全)

记忆口诀 🎵

RDB好比拍照片,
定时保存一瞬间。
文件小来恢复快,
两次快照数据险。

AOF犹如流水账,
命令一条条记详。
数据安全丢失少,
文件大来恢复长。

混合持久是首选,
快照日志两结合。
恢复速度数据全,
生产环境它最佳!

最佳实践清单 ✅

☑ 开启AOF(appendonly yes)
☑ 使用everysec策略(平衡性能和安全)
☑ 开启混合持久化(aof-use-rdb-preamble yes)
☑ 配置自动AOF重写(防止文件过大)
☑ 定期备份RDB文件(异地备份)
☑ 监控fork耗时和内存占用
☑ 主从分离持久化(主库关闭,从库开启)
☑ 使用SSD提高IO性能
☑ 测试数据恢复流程
☑ 设置磁盘空间告警

📚 面试要点

  1. RDB原理:fork子进程,写时复制,快照式持久化
  2. AOF原理:记录写命令,三种fsync策略
  3. AOF重写:压缩日志,fork子进程,重写缓冲区
  4. 混合持久化:RDB+AOF,兼顾速度和安全
  5. 选择依据:数据重要性、恢复速度、性能要求
  6. 生产配置:混合持久化 + everysec + 主从分离

最后总结:

持久化就像给数据上保险 🛡️:

  • RDB = 短期意外险(快但保障有限)
  • AOF = 长期全保险(安全但成本高)
  • 混合持久化 = 组合险(最佳选择)

记住:没有持久化的Redis,就像走钢丝不系安全绳! ⚠️

加油,Redis运维工程师!💪