外观
持久化:RDB 与 AOF 怎么选
这个配置决定的是宕机时最多丢多少数据。选错不是"性能差一点",是丢单、丢消息、丢用户操作记录。
场景:先想清楚能丢多少
| 业务 | 能接受的数据丢失 | 选择 |
|---|---|---|
| 纯缓存,能回源数据库 | 丢 5 分钟无所谓 | 只开 RDB |
| 缓存 + 少量计数/会话 | 丢 1 秒 | RDB + AOF everysec |
| 队列、订单状态、分布式锁审计 | 一条都不能丢 | AOF always + 主从 + 定期备份 |
| 只做排行榜、可重算 | 全丢也能重建 | 可以关持久化,但要写进文档 |
Redis 7 的默认配置是 RDB 开、AOF 关,也就是最弱的一档。所以线上第一件事是确认这两项。
一、看清当前状态
bash
redis-cli -a '密码' config get save
redis-cli -a '密码' config get appendonly
redis-cli -a '密码' config get appendfsync
redis-cli -a '密码' info persistence输出:
text
1) "save"
2) "3600 1 300 100 60 10000"
1) "appendonly"
2) "no"
1) "appendfsync"
2) "everysec"
# Persistence
loading:0
async_loading:0
current_cow_peak:0
current_cow_size:0
current_fork_perc:0.00
rdb_changes_since_last_save:137
rdb_bgsave_in_progress:0
rdb_last_save_time:1761549124
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:1
rdb_current_bgsave_time_sec:-1
rdb_saves:42
rdb_last_cow_size:18579456
rdb_last_load_keys_expired:0
rdb_last_load_keys_loaded:18234
aof_enabled:0
# AOF
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_bgrewrite_status:ok
aof_rewrites:0
aof_last_write_status:ok解释:
save "3600 1 300 100 60 10000"是三个快照点:3600 秒内 1 次写、300 秒内 100 次写、60 秒内 10000 次写,满足任一条件就 fork 一次BGSAVE。rdb_changes_since_last_save:137是当前"还没落盘"的写次数,也就是此刻宕机会丢的量。rdb_last_bgsave_status:ok必须盯,变成err通常是磁盘满。rdb_last_cow_size是 fork 期间因写时复制额外占的内存,几十 MB 正常,上 GB 说明BGSAVE期间写入太猛。- Redis 7 把 AOF 相关字段挪到了
# AOF段,aof_enabled仍留在 Persistence 段。
二、RDB:快照
bash
# 手动触发(后台 fork,不阻塞)
redis-cli -a '密码' bgsave
redis-cli -a '密码' lastsave # 返回上次成功落盘的 Unix 时间戳
# 阻塞式,生产禁用
# redis-cli -a '密码' save输出:
text
Background saving started
1761549432文件落在 dir 指定的目录:
bash
ls -lh /var/lib/redis/6379/text
-rw-r--r-- 1 redis redis 21M Oct 27 17:07 dump.rdb关键配置:
ini
# 关掉自动快照,只在低峰用 cron 触发(写放大敏感的场景)
save ""
# 打开快照
save 900 1
save 300 10
save 60 10000
# 压缩:省磁盘,CPU 略增,字符串多的场景收益明显
rdbcompression yes
# 校验和,损坏能早发现
rdbchecksum yes
# 快照失败时是否拒绝写入。默认 yes
stop-writes-on-bgsave-error yes
# 子进程 fork 失败或写盘失败时,多久后重试
rdb-del-sync-files nostop-writes-on-bgsave-error 是双刃剑
默认 yes 时,磁盘满导致 BGSAVE 失败,Redis 会开始拒绝所有写命令并返回 MISCONF Redis is configured to save RDB snapshots...。这是故意的,防止你以为在写其实数据早就没落盘。正确处置是修磁盘,不是把它改成 no。
RDB 的两个硬伤:
- fork 阻塞。
BGSAVE本身不阻塞,但 fork 那一刻要复制页表;堆越大、页越碎,阻塞越久。32G 堆在普通云盘上 fork 能卡 200ms~1s。 - 丢最后一个窗口。两次快照之间的写全在内存里。
三、AOF:命令日志
bash
redis-cli -a '密码' config set appendonly yes
redis-cli -a '密码' config rewrite输出:
text
OK
OKRedis 7 的 AOF 不再是单个文件,而是一个目录:
bash
ls -lh /var/lib/redis/6379/appendonlydir/text
-rw-r--r-- 1 redis redis 4.0K Oct 27 17:12 appendonly.aof.1.base.rdb
-rw-r--r-- 1 redis redis 512 Oct 27 17:12 appendonly.aof.1.incr.aof
-rw-r--r-- 1 redis redis 88 Oct 27 17:12 appendonly.aof.manifest解释:base 是重写后的基线(aof-use-rdb-preamble yes 时是 RDB 格式,加载更快),incr 是增量追加,manifest 记录当前用了哪些分片。多了 multi-part AOF 这层,重写期间不会出现"旧文件删了、新文件没写完"的空窗。
appendfsync 三档怎么选:
| 值 | 行为 | 丢数据 | 性能 |
|---|---|---|---|
always | 每条命令都 fsync | 最多丢 1 条 | 吞吐降到 1/5 以下 |
everysec | 每秒 fsync 一次(默认) | 最多丢 1 秒 | 几乎无损 |
no | 交给内核刷 | 最多丢 30 秒 | 最快,但不可控 |
99% 的场景用 everysec。always 只在"丢一条就是事故"时用,且要接受 QPS 掉一个数量级。
AOF 常用配置:
ini
appendonly yes
appendfsync everysec
no-appendfsync-on-rewrite no # 重写期间不 fsync 会更稳但风险高,保持 no
auto-aof-rewrite-percentage 100 # 比上次重写后体积翻倍就重写
auto-aof-rewrite-min-size 64mb # 小于 64M 不重写
aof-load-truncated yes # 末尾半条命令忽略并记日志,而不是拒绝启动
aof-use-rdb-preamble yes # 7.0 默认手动重写并观察:
bash
redis-cli -a '密码' bgrewriteaof
redis-cli -a '密码' info persistence | grep -E 'aof_rewrite_in_progress|aof_last_bgrewrite_status|aof_last_cow_size'输出:
text
Background append only file rewriting started
aof_rewrite_in_progress:0
aof_last_bgrewrite_status:ok
aof_last_cow_size:9437184别在业务高峰做 BGREWRITEAOF
它同样要 fork 子进程,且子进程会重放整个数据集写新文件,磁盘 IO 会顶满。放到凌晨,或者依赖 auto-aof-rewrite 的阈值自然触发。
四、重启时到底加载谁
顺序是固定的:
appendonly yes→ 只加载 AOF(目录里的 manifest 指向 base + incr)。appendonly no→ 加载dump.rdb。
bash
tail -20 /var/log/redis/6379.logtext
* DB loaded from append only file: 0.412 seconds
* Ready to accept connections tcp如果日志出现 DB loaded from disk: 0.231 seconds,说明走的是 RDB。
这就是最容易踩的坑:你后来开了 AOF,但数据是之前 RDB 里的;或者从库也开了 AOF,重启后加载了自己那份旧的 AOF,于是从库数据和主库对不上。
五、备份:把文件拿走才叫备份
RDB 只在同一台机器上,机器没了数据就没了。
bash
#!/bin/bash
# /usr/local/bin/redis-backup.sh
set -euo pipefail
DATE=$(date +%F_%H%M)
DIR=/var/lib/redis/6379
DEST=/data/backup/redis
redis-cli -a "$REDIS_PASS" bgsave >/dev/null
while [ "$(redis-cli -a "$REDIS_PASS" info persistence | awk -F: '/rdb_bgsave_in_progress/{print $2}' | tr -d '\r')" != "0" ]; do
sleep 1
done
gzip -c "$DIR/dump.rdb" > "$DEST/dump-$DATE.rdb.gz"
find "$DEST" -name 'dump-*.rdb.gz' -mtime +7 -deletetext
20 3 * * * REDIS_PASS='密码' /usr/local/bin/redis-backup.sh >> /var/log/redis/backup.log 2>&1解释:
- 必须先
bgsave再拷,否则拷到的是残缺文件。 - 等
rdb_bgsave_in_progress变 0 再拷,避免读一个正在写的文件。 gzip对 RDB 压缩率通常在 3~5 倍。- 保留 7 天,异地再同步一份才算真备份。
六、恢复演练
bash
systemctl stop redis-6379
cp /data/backup/redis/dump-2026-10-27_0320.rdb.gz /tmp/
gunzip /tmp/dump-2026-10-27_0320.rdb.gz
chown redis:redis /tmp/dump-2026-10-27_0320.rdb
# 先校验再覆盖,别直接盖
redis-check-rdb /tmp/dump-2026-10-27_0320.rdb
mv /var/lib/redis/6379/dump.rdb /var/lib/redis/6379/dump.rdb.bak
mv /tmp/dump-2026-10-27_0320.rdb /var/lib/redis/6379/dump.rdb
rm -rf /var/lib/redis/6379/appendonlydir # 开了 AOF 就必须先挪走 AOF
systemctl start redis-6379
redis-cli -a '密码' dbsize输出:
text
[offset 0] Checking RDB file /tmp/dump-2026-10-27_0320.rdb
[offset 27] AUX FIELD redis-ver = '7.2.5'
[offset 201] AUX FIELD ctime = '1761525600'
[offset 234] Selecting DB ID 0
[offset 18234] Checksum OK
[offset 18234] \o/ RDB looks OK! \o/
[info] 1 keys read
[info] 0 expires
[info] 0 already expired恢复时忘了挪走 AOF
appendonly yes 的情况下 Redis 只读 AOF。你把新 RDB 拷进去,看到 dbsize 还是旧数据,会以为备份是坏的。恢复前必须把 appendonlydir 整体移走。
七、踩过的坑
| 现象 | 根因 | 处置 |
|---|---|---|
写命令全部 MISCONF | BGSAVE 失败(磁盘满/无权限) | df -h、查 dir 权限,别关 stop-writes-on-bgsave-error |
启动报 Short read or OOM loading DB | RDB 文件损坏(拷贝时正在写) | 用备份恢复,redis-check-rdb 确认 |
启动报 Bad file format reading the append only file | AOF 尾部截断(断电) | redis-check-aof --fix 修,再起 |
| 重启后数据变回几小时前 | 开了 AOF 但 AOF 目录是空的/旧的 | 确认 appendonlydir 内容与时间戳 |
| 每秒卡顿一次 | appendfsync always 或磁盘 fsync 慢 | 改 everysec,换 SSD |
| 内存突然涨 1 倍 | fork 期间 COW | 降低写入峰值,或换更大内存/关 THP |
| 从库数据比主库旧很多 | 从库自己开了 AOF 且重启加载本地文件 | 从库 appendonly no,或重建从库 |