Redis 持久化
持久化是将内存中的数据保存到磁盘的过程。Redis 提供了 RDB 和 AOF 两种持久化方式,本章将详细介绍这两种方式的原理和配置。
持久化概述
Redis 作为内存数据库,如果不进行持久化,重启后数据会全部丢失。持久化机制确保数据在服务器重启后能够恢复。
Redis 持久化方式
┌─────────────────────────────────────────────────┐
│ Redis 内存数据 │
└─────────────────────────────────────────────────┘
│
┌─────────────┴─────────────┐
│ │
┌─────────────────┐ ┌─────────────────┐
│ RDB 快照 │ │ AOF 日志 │
│ 二进制文件 │ │ 文本文件 │
│ 定时保存 │ │ 追加写入 │
│ 恢复速度快 │ │ 数据更安全 │
└─────────────────┘ └─────────────────┘
RDB 持久化
RDB(Redis Database)是将某一时刻的内存数据保存到磁盘的二进制文件(默认为 dump.rdb)。
RDB 工作原理
RDB 使用快照的方式保存数据,通过 BGSAVE 命令触发:
- 客户端触发 BGSAVE 命令
- Redis 主进程 fork 子进程 - 使用写时复制(Copy-on-Write)技术
- 子进程将内存数据写入临时 RDB 文件
- 子进程完成,替换旧的 RDB 文件
- 主进程继续处理客户端请求
fork 操作使用写时复制技术,子进程和父进程共享内存页,只有当数据被修改时才会复制,因此 fork 操作通常很快。
RDB 配置
在 redis.conf 中配置 RDB:
# 触发快照的条件
# save <秒数> <变化次数>
save 900 1 # 900 秒内有 1 次变化
save 300 10 # 300 秒内有 10 次变化
save 60 10000 # 60 秒内有 10000 次变化
# 禁用 RDB(注释掉所有 save 或设置空字符串)
# save ""
# RDB 文件名
dbfilename dump.rdb
# 数据目录
dir /var/lib/redis
# 压缩(默认开启)
rdbcompression yes
# 校验和(默认开启)
rdbchecksum yes
# RDB 文件存储失败时是否停止写入
stop-writes-on-bgsave-error yes
RDB 命令
# 手动触发快照(后台执行)
BGSAVE
# 手动触发快照(前台执行,会阻塞)
SAVE
# 获取上次保存时间
LASTSAVE
# 查看持久化状态
INFO persistence
RDB 优缺点
优点:
- 文件紧凑,适合备份
- 恢复速度快
- 对性能影响小(子进程执行)
- 适合灾难恢复
缺点:
- 数据安全性较低(可能丢失几分钟数据)
- fork 大数据集时可能阻塞
AOF 持久化
AOF(Append Only File)记录所有写操作命令,恢复时重新执行这些命令。
AOF 工作原理
AOF 以日志的形式记录每个写操作,文件以 Redis 协议格式追加保存。每次执行改变数据集的命令时,Redis 会将该命令追加到 AOF 文件末尾。
Redis 7.0+ 的 Multi-Part AOF:
Redis 7.0 重构了 AOF 架构,将单个 AOF 文件拆分为多个文件:
| 文件类型 | 说明 |
|---|---|
| Base AOF | 重写时生成的基础快照(RDB 或 AOF 格式) |
| Incremental AOF | 增量日志文件,记录上次 Base 后的变更 |
| Manifest File | 清单文件,追踪所有 AOF 文件的状态 |
Multi-Part AOF 的优势:
| 维度 | Redis < 7.0 | Redis >= 7.0 |
|---|---|---|
| 重写期间内存压力 | 高(内存缓冲区) | 低(直接写入增量文件) |
| 磁盘写入 | 双写(旧 AOF + 新 AOF) | 单写(增量文件) |
| 重写失败风险 | 可能丢失缓冲区数据 | 数据安全(增量文件持久化) |
| 文件管理 | 单文件 | 多文件,自动清理旧文件 |
目录结构示例:
appendonlydir/
├── appendonly.aof.1.base.rdb # Base AOF(RDB 格式)
├── appendonly.aof.1.incr.aof # 第一个增量文件
├── appendonly.aof.2.incr.aof # 第二个增量文件
└── appendonly.aof.manifest # 清单文件
AOF 配置
# redis.conf 配置
# 启用 AOF
appendonly yes
# AOF 文件名
appendfilename "appendonly.aof"
# AOF 目录(Redis 7.0+,存放所有 AOF 相关文件)
appenddirname "appendonlydir"
# 同步策略
# always : 每次写入都同步,最安全但最慢
# everysec : 每秒同步一次(推荐,平衡性能和安全)
# no : 由操作系统决定,最快但不安全
appendfsync everysec
# 重写期间是否禁用 fsync
no-appendfsync-on-rewrite no
# AOF 重写触发条件
auto-aof-rewrite-percentage 100 # 文件比上次重写后增长 100%
auto-aof-rewrite-min-size 64mb # 文件最小 64MB
# 加载损坏的 AOF 文件是否继续
aof-load-truncated yes
# 使用 RDB-AOF 混合格式(Redis 4.0+,强烈推荐)
aof-use-rdb-preamble yes
AOF 重写
AOF 文件会随着写操作不断增长,重写可以压缩文件大小。
重写原理
Redis >= 7.0 的重写流程:
┌─────────────────────────────────────────────────────────────┐
│ Redis 7.0+ AOF 重写流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 主进程 fork 子进程 │
│ │
│ 2. 主进程打开新的增量文件继续记录新写入 │
│ └── 不再使用内存缓冲区,直接写入磁盘 │
│ │
│ 3. 子进程生成新的 Base AOF 文件 │
│ └── 包含当前数据集的最小命令集合 │
│ │
│ 4. 子进程完成后,主进程创建临时 manifest 文件 │
│ └── 记录新的 Base 和增量文件 │
│ │
│ 5. 原子替换 manifest 文件 │
│ └── 使用 rename(2) 系统调用 │
│ │
│ 6. 清理旧的 Base 文件和无用的增量文件 │
│ │
└─────────────────────────────────────────────────────────────┘
Redis < 7.0 的重写流程:
┌─────────────────────────────────────────────────────────────┐
│ Redis < 7.0 AOF 重写流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 主进程 fork 子进程 │
│ │
│ 2. 主进程将新写入缓存到内存缓冲区 │
│ └── 同时继续写入旧 AOF 文件(保证安全) │
│ │
│ 3. 子进程生成新的 AOF 文件 │
│ │
│ 4. 子进程完成后,主进程将缓冲区追加到新文件 │
│ └── 此时可能阻塞客户端 │
│ │
│ 5. 原子重命名新文件替换旧文件 │
│ │
└─────────────────────────────────────────────────────────────┘
对比说明:
| 阶段 | Redis < 7.0 | Redis >= 7.0 |
|---|---|---|
| 重写期间新写入 | 内存缓冲区 + 旧 AOF | 新增量文件 |
| 内存压力 | 高 | 低 |
| 阻塞风险 | 追加缓冲区时可能阻塞 | 无阻塞 |
| 数据安全 | 依赖内存缓冲区 | 直接持久化到磁盘 |
手动触发重写
# 手动触发重写
BGREWRITEAOF
# 查看重写状态
INFO persistence
# 关注 aof_rewrite_in_progress 和 aof_last_bgrewrite_status
重写限制机制
Redis 7.0 引入了重写限制机制,避免重复失败导致频繁重试:
- 首次失败后等待 1 分钟重试
- 再次失败后等待 2 分钟
- 依次类推,最长等待 1 小时
AOF 文件修复
截断文件(文件末尾不完整):
# Redis 会自动处理截断的 AOF 文件
# 默认配置 aof-load-truncated yes 会自动丢弃末尾不完整的命令
# 手动修复
redis-check-aof --fix appendonly.aof
损坏文件(中间有无效字节):
# 1. 先备份文件
cp appendonly.aof appendonly.aof.backup
# 2. 不带 --fix 检查,定位问题
redis-check-aof appendonly.aof
# 3. 修复(会丢弃损坏位置之后的数据)
redis-check-aof --fix appendonly.aof
AOF 优缺点
优点:
- 数据更安全(最多丢失 1 秒数据)
- AOF 文件可读,便于分析
- 文件损坏可修复
- Redis 7.0+ 重写期间无阻塞
缺点:
- 文件体积较大
- 恢复速度较慢
- Redis < 7.0 重写期间有内存压力
RDB 与 AOF 对比
| 特性 | RDB | AOF |
|---|---|---|
| 数据安全性 | 较低(分钟级丢失) | 较高(秒级丢失) |
| 文件大小 | 小 | 大 |
| 恢复速度 | 快 | 慢 |
| 系统资源消耗 | 低(fork 时) | 较高(持续写入) |
| 文件可读性 | 二进制 | 文本 |
| 适用场景 | 备份、主从复制 | 数据安全要求高 |
混合持久化
Redis 4.0 引入混合持久化,结合 RDB 和 AOF 的优点:
# 启用混合持久化
aof-use-rdb-preamble yes
工作原理:
- AOF 重写时,先写入 RDB 格式的快照
- 然后追加增量的 AOF 命令
- 恢复时先加载 RDB 部分,再执行 AOF 命令
这样既有 RDB 的快速恢复,又有 AOF 的数据安全性。
持久化配置建议
场景一:纯缓存(可接受数据丢失)
# 关闭持久化
save ""
appendonly no
场景二:数据安全要求高
# 开启 AOF
appendonly yes
appendfsync everysec
# 也保留 RDB 用于备份
save 900 1
save 300 10
场景三:平衡方案(推荐)
# 开启 AOF 和混合持久化
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
# RDB 用于快速重启和备份
save 900 1
save 300 10
save 60 10000
数据恢复
RDB 恢复
# 1. 停止 Redis
redis-cli shutdown
# 2. 将 dump.rdb 复制到数据目录
cp /backup/dump.rdb /var/lib/redis/
# 3. 启动 Redis
redis-server /etc/redis/redis.conf
AOF 恢复
# 1. 停止 Redis
redis-cli shutdown
# 2. 复制 AOF 文件
cp /backup/appendonly.aof /var/lib/redis/
# 3. 修复损坏的 AOF(如需要)
redis-check-aof --fix appendonly.aof
# 4. 启动 Redis
redis-server /etc/redis/redis.conf
从 RDB 切换到 AOF
# 在线切换(Redis 2.2+)
redis-cli CONFIG SET appendonly yes
redis-cli CONFIG SET save ""
# 持久化配置
redis-cli CONFIG REWRITE
备份策略
#!/bin/bash
# Redis 备份脚本
BACKUP_DIR="/backup/redis"
DATE=$(date +%Y%m%d_%H%M%S)
# 创建备份目录
mkdir -p $BACKUP_DIR
# 触发 RDB 快照
redis-cli BGSAVE
# 等待快照完成
sleep 5
# 复制 RDB 文件
cp /var/lib/redis/dump.rdb $BACKUP_DIR/dump_$DATE.rdb
# 压缩
gzip $BACKUP_DIR/dump_$DATE.rdb
# 删除 7 天前的备份
find $BACKUP_DIR -name "*.gz" -mtime +7 -delete
echo "Backup completed: dump_$DATE.rdb.gz"
将脚本添加到 crontab 定时执行:
# 每天凌晨 2 点备份
0 2 * * * /path/to/backup.sh
小结
本章我们学习了:
- RDB 持久化:快照方式,恢复快但可能丢数据
- AOF 持久化:日志方式,数据安全但文件大
- 混合持久化:结合两者优点
- 配置建议:根据场景选择合适策略
- 数据恢复:RDB 和 AOF 的恢复方法
练习
- 配置 RDB 持久化并手动触发快照
- 配置 AOF 持久化并观察文件变化
- 模拟 Redis 重启并恢复数据
- 编写自动备份脚本