Redis持久化RDB与AOF

Redis持久化RDB与AOF

Redis 的五种数据结构各有特色,用对了才能发挥它的优势。很多人只用到了 String 和 Hash,却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发,讲什么时候用什么类型。

一、持久化概述

#

1.1 为什么需要持久化

  • Redis数据存储在内存,进程退出数据丢失
  • 持久化将内存数据保存到磁盘
  • 用于重启恢复、备份、复制同步

#

1.2 两种持久化方式

方式 说明 优点 缺点
RDB 快照,保存某一时刻的数据 文件紧凑,恢复速度快 可能丢数据
AOF 记录写操作日志 数据安全性高 文件大,恢复慢

二、RDB持久化

#

2.1 什么是RDB

RDB(Redis Database)是将某一时刻的内存数据生成快照保存到磁盘:

内存数据 ──fork──> 子进程 ──写入──> dump.rdb

#

2.2 触发方式

手动触发

SAVE        # 阻塞主进程,直到完成
BGSAVE # 后台执行,不阻塞

自动触发(配置文件):

# redis.conf
save 900 1 # 900秒内至少有1个key变化
save 300 10 # 300秒内至少有10个key变化
save 60 10000 # 60秒内至少有10000个key变化

其他触发

  • 执行FLUSHALL/FLUSHDB(如果配置了save)
  • 主从复制时,从节点发起同步
  • 执行SHUTDOWN且没有开启AOF

#

2.3 BGSAVE的fork机制

主进程                    子进程
│ │
├── fork() ─────────────>│
│ (瞬间完成) │
│ │
│ 继续服务客户端 ├── 扫描内存
│ │ 写入RDB文件
│ │
│ <─────────────────────┤
│ 子进程退出 │ 写入完成

fork的过程

// Redis调用fork创建子进程
pid_t childpid = fork();
if (childpid == 0) {
// 子进程:生成RDB文件
rdbSave(filename);
exit(0);
} else {
// 父进程:继续处理客户端请求
}

Copy-On-Write(写时复制)

fork后内存状态:

父进程页表 子进程页表
┌─────────┐ ┌─────────┐
│ 虚拟页1 │──┬──> │ 虚拟页1 │──┬──> 物理页1
│ 虚拟页2 │──┼──> │ 虚拟页2 │──┼──> 物理页2
└─────────┘ │ └─────────┘ │
└───────────────────┘
共享物理内存(只读)

父进程修改数据后:

父进程页表 子进程页表
┌─────────┐ ┌─────────┐
│ 虚拟页1 │──┬──> │ 虚拟页1 │──┬──> 物理页1(旧)
│ 虚拟页2 │──┼──> │ 虚拟页2 │──┘
└─────────┘ │ └─────────┘
└──> 物理页2'(新,复制)

父进程修改虚拟页2时:
1. 复制物理页2到新页2'
2. 父进程页表指向新页2'
3. 子进程页表仍指向旧页2

COW的优势

  • fork瞬间完成,不拷贝数据
  • 只有修改的页才会复制
  • 子进程看到fork时的数据快照

#

2.4 RDB文件格式

RDB文件结构:
┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ REDIS │ VERSION │ SELECT │ DB DATA│ SELECT │ EOF │
│ magic │ 0009 │ DB 0 │ │ DB 1 │ + CRC │
│ 5字节 │ 4字节 │ │ │ │ │
└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘

DB DATA格式:
┌─────────┬─────────┬─────────┬─────────┐
│ EXPIRE │ TYPE │ KEY │ VALUE │
│ (可选) │ │ │ │
└─────────┴─────────┴─────────┴─────────┘

#

2.5 RDB配置

# redis.conf

# RDB文件名
dbfilename dump.rdb

# RDB文件保存目录
dir /var/lib/redis

# 停止写入如果RDB失败
stop-writes-on-bgsave-error yes

# RDB文件压缩
rdbcompression yes

# RDB文件校验
rdbchecksum yes

#

2.6 RDB的优缺点

优点

  • 文件紧凑,适合备份和灾难恢复
  • 恢复速度快(直接加载到内存)
  • 对性能影响小(子进程处理)

缺点

  • 可能丢失最后一次save后的数据
  • fork时内存占用可能翻倍(COW期间)

三、AOF持久化

#

3.1 什么是AOF

AOF(Append Only File)记录每个写操作命令:

客户端: SET key value
Redis: 将命令追加到AOF文件

AOF文件内容:
*3\r
$3\r
SET\r
$3\r
key\r
$5\r
value\r

#

3.2 AOF持久化流程

客户端写命令


┌─────────────┐
│ AOF缓冲区 │ ← 先写入内存缓冲区
└──────┬──────┘

├── sync策略 ──>


┌─────────────┐
│ AOF文件 │ ← 同步到磁盘
└─────────────┘

#

3.3 AOF同步策略

# redis.conf

# 每次写入都fsync(最安全,最慢)
appendfsync always

# 每秒fsync一次(推荐,平衡安全性和性能)
appendfsync everysec

# 由操作系统决定(最快,最不安全)
appendfsync no
模式 说明 数据丢失 性能
always 每次写入fsync 0
everysec 每秒fsync <=1秒
no OS决定 可能很多

推荐:everysec(默认)

#

3.4 AOF重写

为什么需要重写

AOF文件不断增长:
SET key value1
SET key value2
SET key value3
...

同一个key多次修改,只有最后一次有效
需要压缩只保留最终状态

重写原理

┌─────────────┐      fork      ┌─────────────┐
│ 旧AOF文件 │ ──────────> │ 子进程 │
│ (很大) │ │ 读取内存 │
└─────────────┘ │ 生成新AOF │
└──────┬──────┘


┌─────────────┐
│ 新AOF文件 │
│ (紧凑) │
└─────────────┘

重写期间,新的写命令同时写入:
1. 旧AOF文件(继续追加)
2. AOF重写缓冲区(子进程结束后追加到新文件)

触发方式

# 自动触发
auto-aof-rewrite-percentage 100 # AOF文件增长100%
auto-aof-rewrite-min-size 64mb # 最小64MB才触发

# 手动触发
BGREWRITEAOF

#

3.5 AOF配置

# 开启AOF
appendonly yes

# AOF文件名
appendfilename "appendonly.aof"

# 同步策略
appendfsync everysec

# 重写时是否禁用fsync(减少IO压力)
no-appendfsync-on-rewrite no

# 自动重写配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 加载AOF时是否忽略末尾错误(如AOF被截断)
aof-load-truncated yes

# 使用RDB前缀加速AOF恢复(Redis 4.0+)
aof-use-rdb-preamble yes

#

3.6 AOF的优缺点

优点

  • 数据安全性高(everysec只丢1秒)
  • AOF文件可读,可手动修复
  • 支持重写压缩

缺点

  • 文件比RDB大
  • 恢复速度比RDB慢
  • 对性能有一定影响

四、RDB与AOF对比

特性 RDB AOF
文件大小 小(压缩) 大(文本命令)
恢复速度
数据安全 可能丢数据 只丢1秒(everysec)
对性能影响 fork时短暂影响 持续影响(fsync)
可读性 二进制,不可读 文本,可读可修复
适合场景 备份、灾难恢复 数据安全要求高

五、持久化方案选择

#

5.1 推荐方案

方案一:RDB + AOF(推荐)

# 同时开启RDB和AOF
save 900 1
save 300 10
save 60 10000

appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
  • RDB用于快速恢复和备份
  • AOF保证数据安全
  • Redis 4.0+支持混合持久化

方案二:仅RDB

save 900 1
appendonly no
  • 适合缓存场景,可容忍数据丢失
  • 性能最好

方案三:仅AOF

save ""
appendonly yes
appendfsync everysec
  • 数据安全要求极高
  • 禁用RDB save

#

5.2 混合持久化(Redis 4.0+)

aof-use-rdb-preamble yes

AOF文件格式:

┌─────────────────┬─────────────────────┐
│ RDB格式前缀 │ AOF格式追加命令 │
│ (全量数据) │ (增量命令) │
└─────────────────┴─────────────────────┘

恢复时:
1. 先加载RDB前缀(快速)
2. 再执行AOF命令(精确)

优势

  • 恢复速度接近RDB
  • 数据安全性接近AOF

六、恢复流程

#

6.1 启动时恢复

Redis启动

├── AOF开启?
│ ├── 是 → 加载AOF文件
│ │ └── 成功 → 启动完成
│ │ └── 失败 → 报错退出(可配置截断加载)
│ └── 否 → 加载RDB文件
│ └── 成功 → 启动完成
│ └── 失败 → 空数据启动

└── 如果AOF和RDB都存在,优先AOF

#

6.2 灾难恢复

# 1. 停止Redis
redis-cli SHUTDOWN

# 2. 备份当前文件
cp appendonly.aof appendonly.aof.bak
cp dump.rdb dump.rdb.bak

# 3. 恢复备份文件
cp /backup/dump.rdb /var/lib/redis/
# 或
cp /backup/appendonly.aof /var/lib/redis/

# 4. 启动Redis
redis-server /etc/redis/redis.conf

# 5. 检查数据完整性
redis-cli INFO keyspace

七、常见问题

#

7.1 fork耗时过长

现象:BGSAVE或AOF重写时,Redis卡顿

原因

  • 内存过大,fork需要时间
  • 系统swap使用

解决

# 监控fork耗时
redis-cli INFO stats | grep latest_fork_usec

# 优化:控制Redis内存大小
# 或启用大页(如果系统支持)
echo never > /sys/kernel/mm/transparent_hugepage/enabled

#

7.2 AOF文件过大

现象:AOF文件持续增长

解决

# 手动触发重写
redis-cli BGREWRITEAOF

# 检查重写配置
redis-cli CONFIG GET auto-aof-rewrite*

#

7.3 持久化失败

现象:Redis日志报错 “Can’t save in background”

原因

  • 内存不足(COW需要额外内存)
  • 磁盘空间不足
  • 权限问题

解决

# 检查磁盘空间
df -h

# 检查内存
free -h

# 检查目录权限
ls -la /var/lib/redis/

八、总结

场景 推荐方案
数据安全要求高 RDB + AOF(everysec)
纯缓存场景 仅RDB或关闭持久化
快速恢复 混合持久化(4.0+)
大数据量 RDB为主,定期备份

持久化配置的核心原则:

  1. 同时开启RDB和AOF(生产环境)
  2. AOF使用everysec模式
  3. 定期验证备份可用性
  4. 监控fork耗时和持久化状态
  5. 确保足够的磁盘空间和内存

核心要点

  1. String:简单的键值对,适合缓存、计数器

  2. Hash:存储对象属性,适合用户信息、配置

  3. List:有序列表,适合消息队列、最新列表

  4. Set:无序去重,适合共同好友、抽奖

  5. ZSet:有序集合,适合排行榜、积分系统

总结

选择合适的数据结构是使用 Redis 的关键。在实际项目中,根据业务需求选择合适的类型,可以提升性能和开发效率。


   转载规则


《Redis持久化RDB与AOF》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录