一、Redis 重启之后数据没了,是谁的责任
很多站长第一次接触 Redis 是为了做缓存,用完发现「反正丢了会重建」,于是从来不关心持久化。等到某一天把会话、队列、排行榜、限流计数、验证码、甚至一部分业务数据都放进 Redis 之后,一次重启或一次 OOM 就把这些东西全带走了。这时候再回头看配置,才发现持久化一直是关着的,或者开着但从来没验证过。
Redis 的持久化有两套机制:RDB 是「定时拍快照」,AOF 是「记录每一条写命令」。两者可以单独用,也可以同时用,还可以在主从架构里让从库承担持久化的职责。选哪一种、怎么配、多久备份一次、怎么验证备份真的能用,是这篇文章要讲清楚的四件事。
先把话说在前面:持久化不是备份。持久化解决的是「进程重启后数据还在」,备份解决的是「磁盘坏了、机器没了之后数据还在」。这两个问题的答案完全不同,混在一起是最大的认知错误。
二、RDB:简单、快,但会丢最后一段时间
RDB 的机制是 fork 一个子进程,把当前内存里的数据写成一份二进制文件,默认叫 dump.rdb。它的优点是文件紧凑、恢复速度极快(大实例冷启动几秒到几十秒)、对主进程的性能影响小。缺点是两次快照之间的数据没有落盘,进程崩溃就丢了。
# redis.conf 中最基本的三行
save 900 1 # 900 秒内至少 1 次写入就快照
save 300 10 # 300 秒内至少 10 次写入
save 60 10000 # 60 秒内至少 10000 次写入
dbfilename dump.rdb
dir /var/lib/redis这三行的含义是「满足任一条件就触发」。默认配置看起来合理,但对数据敏感的站点意味着最坏情况下丢 15 分钟的数据。如果你的 Redis 里放了会话或者队列,这个窗口显然不能接受,需要按业务的可接受丢失量重新设计:能接受丢 1 分钟,就得配 save 60 1 之类更激进的规则,而更激进意味着更频繁的 fork,对内存和 CPU 都是压力。
RDB 有一个最容易踩的坑:fork 的瞬间,父子进程共享内存页,但一旦有写入,内核就会为被修改的页复制一份(写时复制)。如果实例很大、写入又集中,fork 期间的内存峰值可能接近两倍,机器内存不够就会触发 OOM Killer,而它可能直接杀掉 Redis 主进程。判断方法是在日志里搜 Background saving started 前后有没有 Can't save in background 或者被系统杀掉的痕迹。
三、AOF:丢得少,但要有重写策略
AOF 把每一条写命令追加到文件末尾。它的优点是可以做到几乎不丢数据,缺点是文件会持续膨胀,而且恢复时要重放所有命令,比 RDB 慢得多。所以 AOF 必须配合重写(rewrite):当文件膨胀到一定比例时,Redis 会 fork 一个子进程,把当前的完整数据集重写成一份最小的命令集合。
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec # 每秒 fsync,最坏丢 1 秒
# 重写触发条件:文件比上次重写后增长 100%,且大于 64MB
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 重写期间是否阻塞 fsync,默认 no 更安全
no-appendfsync-on-rewrite noappendfsync 有三个选项:always 每条命令都 fsync,最安全但性能最差(写入降一个数量级);everysec 每秒一次,是绝大多数场景的正确答案;no 完全交给操作系统,性能最好但可能丢几十秒。
重写也有一个连锁问题:重写时 fork 子进程,如果同时又在 fsync,磁盘 IO 会被争抢,进而拖慢主线程。经验值是「磁盘 IO 越差,appendfsync 就越不能激进」,在一块普通云盘上配 always,写入延迟会肉眼可见地变差。
四、混合持久化:两者的折中方案
从 Redis 4.0 起提供了一个实用选项:AOF 重写时把当前数据集以 RDB 格式写在文件开头,之后的增量命令继续以文本形式追加。这样既保留了 RDB 的快速加载,又保留了 AOF 的低丢失量。开启方式只有一行:
aof-use-rdb-preamble yes这个选项在新版本里是默认开启的,但很多老配置从旧版本迁移过来时保留了 no。判断方式:看 AOF 文件的开头几个字节,如果是 REDIS 那就是 RDB 格式的头部,说明混合持久化生效;如果是 *2 之类的文本命令,说明还是纯 AOF。
在实际运行中,我倾向的方案是:AOF 打开、everysec、打开混合持久化、同时保留 RDB 快照作为第二份。两套文件写入不同的目录或不同的磁盘,一份损坏还有另一份可以兜底。代价是磁盘占用翻倍,但这两个文件加起来通常还是几百 MB 的量级,对现在的磁盘来说不算什么。
五、持久化不是备份:三份数据与两个位置
把 RDB 和 AOF 都配好之后,数据依然可能全丢。原因包括:磁盘故障、误执行 FLUSHALL、被入侵后数据被删、云主机被销毁。这几种情况下,持久化文件本身就是被破坏的对象,救不了你。
一个可执行的备份规则是「三份数据、两个不同位置」:本机一份(持久化文件本身),本地另一个磁盘或者另一台机器一份,异地对象存储一份。第三份最关键,但也是最多人跳过的。
# 每天凌晨把 RDB 推到对象存储,文件名带日期
#!/bin/sh
D=$(date +%Y%m%d)
redis-cli BGSAVE
# 等后台保存完成,别用固定 sleep
while [ "$(redis-cli INFO persistence | awk -F: '/rdb_bgsave_in_progress/{print $2}' | tr -d '\r')" != "0" ]; do
sleep 2
done
rclone copy /var/lib/redis/dump.rdb remote:redis-backup/$D/ --s3-no-check-bucket
# 只保留最近 14 天
rclone delete remote:redis-backup --min-age 14d这个脚本里有两个关键点:一是必须在 BGSAVE 之后轮询 rdb_bgsave_in_progress,不能用固定 sleep,因为实例大的时候快照可能要几十秒;二是复制目标文件名要带日期,否则每次覆盖,出了问题没有历史版本可回。
六、验证备份能不能用,而不是有没有生成
「备份文件存在」和「备份能恢复」是两件完全不同的事。判断方法很直接:在另一台机器上,用备份文件起一个临时 Redis,看看数据是否完整。
# 1) 在测试机上用备份文件启动一个临时实例
mkdir -p /tmp/rdbtest && cp dump.rdb /tmp/rdbtest/
redis-server --port 6399 --dir /tmp/rdbtest --dbfilename dump.rdb \
--save '' --appendonly no --daemonize yes
# 2) 等它加载完成,然后核对关键指标
sleep 2
redis-cli -p 6399 DBSIZE
redis-cli -p 6399 INFO keyspace
redis-cli -p 6399 --scan --pattern 'session:*' | wc -l
# 3) 抽查几条真实数据,确认内容不是空的
redis-cli -p 6399 GET "$(redis-cli -p 6399 --scan --pattern 'session:*' | head -1)"
# 4) 用完关掉
redis-cli -p 6399 SHUTDOWN NOSAVE这四步要纳入月度检查。第 2 步的 DBSIZE 应该和线上同一个量级,第 3 步读出来的值应该是完整的内容而不是空字符串。如果加载失败,Redis 会在日志里明确写 Bad file format reading the append only file 或 Wrong signature trying to load DB from file,这两种错误分别对应 AOF 损坏和 RDB 损坏,处置方式不同:前者通常可以用 redis-check-aof --fix 截断尾部修复(会丢最后一点数据),后者基本只能换一份备份。
七、几种常见的误判
误判一:看到 rdb_last_bgsave_status:ok 就以为数据安全。这个字段只说明最后一次快照成功了,不说明快照文件被拷到了别处,也不说明你最近验证过。磁盘坏掉的时候它依然会是 ok,因为进程到那一刻为止都没失败。
误判二:主从复制等于备份。从库是实时的镜像,主库误执行 FLUSHALL,从库会在毫秒级内同步执行同样的操作。主从解决的是可用性,不是备份。同理,从库上做的 RDB 快照如果发生在这个时间窗内,也已经被污染了。
误判三:AOF 开着就不会丢数据。appendfsync everysec 的最坏情况下会丢一秒;如果磁盘故障导致 fsync 长期失败,Redis 会拒绝写入并报 MISCONF Errors writing to the AOF file,此时业务其实已经在报错了。看到这个报错不要急着重启,先检查磁盘空间与 diskstats,重启只会让问题重现。
误判四:磁盘满了和持久化无关。磁盘满的时候,RDB 快照写不出来、AOF 追加失败,两个机制会同时失效;而 Redis 本身还在正常提供读写服务,因为数据都在内存里。等你发现的时候,可能已经连续几天没有任何持久化产物了。所以磁盘空间监控和持久化状态监控必须绑在一起看。
八、上线顺序与日常检查
调整持久化配置是有风险的,标准顺序是:先开 AOF(此时会触发一次完整的重写,磁盘 IO 会上升,建议放在低峰期),观察一天确认写入延迟没有明显变差;再调整 appendfsync;最后才考虑关闭 RDB,或者把 RDB 保留为第二份。反过来「先关 RDB 再开 AOF」会有一个谁都没兜底的空窗期,不要这么干。
日常检查只需要看四个数字:rdb_last_bgsave_status、aof_last_write_status、rdb_last_save_time 与备份文件的最后修改时间。前两个不是 ok 就是故障,第三个如果距今超过一天说明快照没在跑,第四个如果比第三个还旧说明备份推送断掉了。这四个数可以写进一条 redis-cli INFO persistence 的输出里,做成每天一封的报表。
九、小结
RDB 快、AOF 全,混合持久化是两者的合理折中;但无论选哪种,都要记住持久化只解决「进程重启」,备份才解决「机器没了」。真正决定数据能不能救回来的,不是你配置了多少个 save 规则,而是你有没有每月真的用备份文件起过一个实例、数过里面的键。把验证做进流程,比把参数调到极致重要得多。