Skip to content

持久化

Redis 以内存为数据主存位置,进程退出后内存数据就没了。持久化的目标是把内存数据保存到磁盘,重启时加载回来。它不能承诺"绝对不丢"——RDB 丢的是快照之后那一整段,AOF 取决于 fsync 策略也可能丢最后 1 秒。持久化是在"丢多少数据"和"写多少磁盘"之间找平衡。

一、RDB——时间点快照

RDB 是某个时间点的完整快照,生成一个紧凑的二进制文件(通常是 dump.rdb)。

配置:

conf
dir /data/redis
dbfilename dump.rdb

save 900 1       # 900 秒内至少 1 次写 → 触发
save 300 10      # 300 秒内至少 10 次写 → 触发
save 60 10000    # 60 秒内至少 10000 次写 → 触发

save 规则之间是"或"的关系,任意一条满足就触发 BGSAVE。规则太少 → 快照间隔长 → 可能丢更多数据。规则太密集 → BGSAVE 频繁 → fork 和磁盘 IO 频繁影响性能。

手动触发生成 RDB:

bash
redis-cli BGSAVE    # 后台 fork 子进程生成 RDB,不阻塞主线程
redis-cli SAVE      # 前台生成,阻塞所有请求直到完成——生产不要用

BGSAVE 的核心代价是 fork。fork 时 OS 需要为子进程创建虚拟地址空间(和父进程一样大),大内存实例上这个操作本身可能造成几百毫秒到秒级的延迟抖动。如果 vm.overcommit_memory 没设好或机器内存实际不够,fork 还可能直接失败。

查看 RDB 状态:

bash
redis-cli INFO persistence | grep -E 'rdb_last_bgsave_status|rdb_last_save_time|rdb_changes_since_last_save'
字段含义
rdb_last_bgsave_status最近一次 BGSAVE 是 ok 还是 err
rdb_last_save_time最近一次成功保存的 Unix 时间戳
rdb_changes_since_last_save上次保存以来累积了多少写变更——如果这个数字很大且持续增长,说明最近没有成功快照

rdb_last_bgsave_status 是备份脚本要检查的字段——dump.rdb 文件存在不代表最近一次保存成功了,可能是一个星期前的旧文件。

二、AOF——写命令追加

AOF(Append Only File)把每次写命令追加到日志文件,Redis 重启时重放这些命令恢复数据。它比 RDB 更接近"实时记录",但数据丢失范围和 fsync 策略直接相关。

配置:

conf
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec

appendfsync 的三个级别:

策略行为丢数据范围性能影响
always每条写命令都 fsync基本不丢磁盘压力最大,QPS 明显下降
everysec每秒 fsync 一次最多丢最后 1 秒常用的平衡点
no交给 OS 决定何时刷最多丢几十秒性能最好,但丢失不可控

everysec 是生产环境最常见的选项——性能损耗可接受,丢失范围有上界。但"最多丢 1 秒"是理论值:如果磁盘本身慢,fsync 本身可能阻塞超过 1 秒,导致积压。

查看 AOF 状态:

bash
redis-cli INFO persistence | grep -E 'aof_enabled|aof_last_bgrewrite_status|aof_last_write_status|aof_current_size'

aof_last_write_status 为 err 时,Redis 可能阻止写入(依赖 stop-writes-on-bgsave-error 的行为),需要立即查磁盘空间和权限。

Redis 7 的多文件 AOF

Redis 7 开始默认使用 multi-part AOF,不再是单个 appendonly.aof 文件。文件结构:

text
dir/appendonlydir/
├── appendonly.aof.manifest    # 清单文件,记录当前 base 和 incr 文件
├── appendonly.aof.1.base.rdb  # 重写后生成的基础文件(RDB 格式)
└── appendonly.aof.1.incr.aof  # 重写后继续追加的增量命令

配置目录名:

conf
appenddirname "appendonlydir"

这个目录在 dir 之下。恢复和备份时,不能只操作单个 appendonly.aof 文件——manifest、base、incr 三者是成组的,缺一个都可能导致数据不完整。备份整个 appendonlydir 目录是最稳妥的做法。

三、AOF 重写——压缩历史命令

AOF 一直追加,文件会越来越大。比如同一个 key 被写了很多次:

text
SET cache:user:1 old
SET cache:user:1 new
SET cache:user:1 final
EXPIRE cache:user:1 300

重写后只需要保留最终状态,不保留中间修改。AOF 重写就是生成一个更紧凑的新文件,把冗余命令压缩掉。

手动触发:

bash
redis-cli BGREWRITEAOF

自动触发配置:

conf
auto-aof-rewrite-percentage 100   # AOF 比上次重写后增长 100% 时触发
auto-aof-rewrite-min-size 64mb    # AOF 至少 64MB 才考虑触发

AOF 重写也会 fork 子进程——和 BGSAVE 一样有 fork 延迟和内存压力的问题。排查 Redis 突然变慢时,可以先看是否有重写在跑:

bash
redis-cli INFO persistence | grep aof_rewrite_in_progress

值为 1 表示重写正在进行中。如果实例很大、磁盘 IO 本来就高,重写期间延迟可能明显上升。

四、混合持久化

混合持久化是 AOF 重写时的优化:生成的新 AOF 文件前半段是 RDB 格式的快照,后半段是增量写命令。这样既保留了 AOF 的增量特性,恢复速度又接近 RDB:

conf
aof-use-rdb-preamble yes

Redis 7 的多文件 AOF 里,base 文件默认就是 RDB 格式,实际上已经隐含了混合持久化的效果。

五、RDB 和 AOF 的加载顺序

同时开启 RDB 和 AOF 时,Redis 重启会优先加载 AOF——因为 AOF 通常包含更完整的数据。启动日志里能看到具体的加载过程:

bash
journalctl -u redis-6379 -n 100 --no-pager
tail -n 100 /var/log/redis/redis-6379.log

如果 AOF 文件损坏,Redis 可能启动失败。检查工具:

bash
# 单文件 AOF(旧版或关闭多文件 AOF 时)
redis-check-aof --fix /data/redis/appendonly.aof

# 多文件 AOF(Redis 7 默认)
redis-check-aof --fix /data/redis/appendonlydir/appendonly.aof.manifest

--fix 可能截断损坏的部分——执行前一定要先备份整个 AOF 目录或文件。截掉了什么、丢了哪些数据,需要在修复后对照业务确认。

RDB 文件检查:

bash
redis-check-rdb /data/redis/dump.rdb

六、备份策略

RDB 文件是天然的备份对象——紧凑、单个文件、恢复速度快。

备份脚本的核心逻辑:触发 BGSAVE → 等待完成 → 确认状态为 ok → 复制文件:

bash
#!/bin/bash
set -euo pipefail

backup_dir="/backup/redis"
data_dir="/data/redis"
stamp="$(date +%F-%H%M%S)"

mkdir -p "$backup_dir"

# 触发后台快照
redis-cli BGSAVE

# 等待 BGSAVE 完成——避免复制到生成中的文件
while [ "$(redis-cli INFO persistence | awk -F: '/rdb_bgsave_in_progress/ {print $2}' | tr -d '\r')" = "1" ]; do
    sleep 1
done

# 确认最近一次保存成功——文件存在不代表成功
status="$(redis-cli INFO persistence | awk -F: '/rdb_last_bgsave_status/ {print $2}' | tr -d '\r')"
if [ "$status" != "ok" ]; then
    echo "rdb backup failed: $status" >&2
    exit 1
fi

cp -a "$data_dir/dump.rdb" "$backup_dir/dump-$stamp.rdb"

这个脚本检查了两样东西:rdb_bgsave_in_progress 确保快照完成,rdb_last_bgsave_status 确保快照成功。不检查状态,只看文件存在,可能备份的是一个星期前生成的老文件。

如果开启了 AOF,备份对象应该包括整个 appendonlydir 目录(Redis 7)或 AOF 文件(旧版)。AOF 的备份不能只靠 RDB 备份替代——AOF 的数据更新,恢复时更接近最新状态。

七、恢复流程

RDB 恢复:

bash
systemctl stop redis-6379
cp -a /data/redis /data/redis.bak.$(date +%F-%H%M%S)   # 先备份当前目录
cp -a /backup/redis/dump-2026-05-22-010000.rdb /data/redis/dump.rdb
chown redis:redis /data/redis/dump.rdb
systemctl start redis-6379
redis-cli DBSIZE   # 启动后确认 key 数量是否符合预期

AOF 恢复同理,但要放回整个 appendonlydir 目录。关键陷阱:同时开启了 AOF 和 RDB 的实例,如果只替换了 RDB 文件但旧的 AOF 还在,Redis 启动时会优先加载 AOF——最终加载的可能不是备份文件里的数据,而是当前目录里残留的旧 AOF。替换 RDB 之前要确认:AOF 是不是也要跟着替换,或者暂时关闭 AOF 让 Redis 只加载 RDB。

八、持久化失败时的保护行为

RDB 后台保存失败后,Redis 有一个保护开关:

conf
stop-writes-on-bgsave-error yes

这个配置的意思是:最近一次 BGSAVE 失败后,Redis 拒绝所有写入请求。设计意图是——"既然无法落盘,就不要让进程继续写新数据,否则重启后丢得更多"。但这对业务来说意味着 Redis 突然不可写。排查时看到写入报错,rdb_last_bgsave_status 和磁盘空间要同时看——很可能是磁盘满了。

如果 Redis 是纯缓存用途(数据可以从其他数据源重建),把这个开关关掉可以避免磁盘问题牵连缓存可用性。如果 Redis 存的是计数器、会话等需要持久化的数据,开着这个开关能更早暴露持久化异常。

九、几种场景的配置组合

场景配置倾向原因
纯缓存,数据能回源可以不开持久化,或只开低频 RDB数据重建成本低,持久化不是必须
会话/限流/计数AOF everysec + RDB数据有状态,丢 1 秒在可接受范围
队列/状态类AOF everysec + 备份 + 主从数据丢失影响较大
大内存实例(>32GB)关注 fork 耗时、重写窗口、磁盘吞吐fork 本身就是操作风险

持久化本质上是"降低丢失范围"的手段,不是数据库级别的最终保险。业务如果完全不能接受丢数据,设计上还要有数据库落地、消息确认或业务补偿机制——Redis 持久化只是多层保护中的一层。