数据库运维13 min read次阅读

Redis 重启即失?RDB、AOF 与混合持久化,以及断电后怎么把数据救回来

有回机房那排机柜短暂掉电,来电后 Redis 起是起来了,但业务一查缓存空了一半。当时配置是默认的 RDB,save 规则还是 Redis 出厂那套 900 1 / 300 10 / 60 10000。问题就在这:最后一次快照是四十多分钟前,中间这四十分钟写入的数据,全随断电蒸发了。DBA 群里一句「不是开了持久化吗」——开了,但开的是会丢最近一段数据的那种。

这件事让我把 Redis 持久化从头理了一遍。它不像 MySQL 那种「写入即落盘」的惯性思维,Redis 的持久化是两套独立机制,各有取舍,而且默认配置对生产并不友好。

先认清楚:Redis 内存数据,凭什么是「可能丢」的

Redis 把数据放在内存里,这是它快的根源,也是它脆的根源。所谓持久化,就是周期性或实时地把内存里的东西,搬到磁盘上一份。Redis 提供两条路:

  • RDB:某一刻内存数据的二进制快照(snapshot)。像给数据库拍张照片。
  • AOF:把每一条写命令追加记录成日志(append only file)。像记流水账。

两者可以单独开、也可以同时开。同时开的时候,Redis 重启优先用 AOF 恢复(因为 AOF 通常更完整),这点后面恢复阶段会再提。

RDB:快照的优势和那道「时间窗」

RDB 的原理是 fork 一个子进程,子进程读父进程的内存、写成 dump.rdb;父进程继续对外服务,靠**写时复制(copy-on-write)**保证fork 那一刻的数据一致性。父进程改了某块内存,内核才把那块复制一份给父进程用,子进程看到的还是旧值——所以子进程写出来的就是 fork 瞬间的完整快照。

手动触发的两条命令:

# 阻塞式,主线程亲自写,期间不响应任何请求,生产禁用
redis-cli SAVE

# 后台异步,fork 子进程写,生产该用这个
redis-cli BGSAVE

配置文件里那些 save 规则,就是「满足 N 秒内 M 次改动就自动 BGSAVE」:

save 900 1       # 15 分钟内有至少 1 次改动
save 300 10      # 5 分钟内有至少 10 次改动
save 60 10000    # 1 分钟内有至少 1 万次改动

还有几个和可靠性、体积相关的:

stop-writes-on-bgsave-error yes   # BGSAVE 失败(比如磁盘满)就停止接受写,防止静默丢数据
rdbcompression yes                # 字符串用 LZF 压缩,省空间但吃一点 CPU
rdbchecksum yes                   # 文件尾写 CRC64,加载时校验完整性
dbfilename dump.rdb
dir /var/lib/redis                # rdb/aof 都放这

RDB 的好处很实在:文件紧凑、恢复极快、适合做冷备和主从全量同步的基线。缺点也同样明显:最近一次快照之后的写入,断电就没了——这个窗口最长就是 save 规则里最大的那个间隔(默认最长 15 分钟,但我们那次实际是四十多分钟没触发,因为那段时间写频率没达到任何一条规则阈值)。

另外一个常被低估的成本是 fork 本身。内存越大,fork 越慢,而且 COW 意味着 fork 后若父进程大量改写,实际物理内存可能短暂翻倍。可以在 info persistence 里看 latest_fork_usec,我见过一台 30G 内存实例 fork 花了近 800 毫秒——这 800 毫秒内虽然不阻塞请求,但 COW 带来的内存压力在那之后陆续释放。

AOF:流水账,能把窗口压到一秒

AOF 把写命令以 Redis 协议格式追加到文件尾。关键在 appendfsync,它决定「命令多久真正落一次盘」:

appendonly yes
appendfsync everysec   # 默认值,每秒 fsync 一次,最多丢 1 秒
# appendfsync always   # 每条命令都 fsync,最安全但最慢,吞吐掉一大截
# appendfsync no       # 交给操作系统,最省 CPU,但丢数据量不可控

everysec 是绝大多数生产的甜点:性能损失小,最坏情况丢最后一秒的数据。追求极致安全的金融场景才上 always,但 QPS 会明显受限。

AOF 的麻烦在于它会无限膨胀——一条 INCR 执行一万次,AOF 里就记一万行,而 RDB 里只是一个最终值。所以 Redis 有 AOF 重写:把当前内存状态重写成一份最小命令集,替代掉那堆积如山的旧日志。

auto-aof-rewrite-percentage 100   # 当前 AOF 比上次重写后涨了 100% 就触发
auto-aof-rewrite-min-size 64mb    # 且文件至少 64MB 才值得重写

重写也是 fork 子进程来做,不阻塞主线程。但有两个坑:

no-appendfsync-on-rewrite no   # 重写期间是否暂停 fsync。设 yes 能减轻磁盘压力,
                               # 但重写期间若宕机,可能丢不止 1 秒(甚至丢整个重写期的数据)
aof-use-rdb-preamble yes       # 4.0+ 混合持久化:重写后的 AOF 以 RDB 格式开头,
                               # 再附增量 AOF 命令,兼顾恢复速度与数据完整性
aof-load-truncated yes         # AOF 尾部损坏时,启动时截断到最后一个完整命令而非直接报错

aof-use-rdb-preamble yes 是我现在生产必开的——它让重写出来的 AOF 文件前面是一段 RDB(恢复快),后面跟上增量命令(数据新),基本是两全其美。

取舍:到底怎么配

没有银弹,看你能接受丢多少、要恢复多快、磁盘和 fork 成本多少:

  • 只开 RDB:适合纯缓存、丢了能再从 DB 灌回来的场景。配置简单、文件小、恢复快,代价是可能丢最近一段。
  • 只开 AOF(everysec):数据最稳(丢 ≤1 秒),代价是文件大、恢复比 RDB 慢、fork 重写有开销。
  • RDB + AOF 都开 + 混合持久化:我现在的默认推荐。RDB 做定期冷备基线,AOF 保最近数据,恢复时 Redis 自己先加载 AOF 里的 RDB 前缀再回放增量。磁盘占用比纯 AOF 小,恢复比纯 AOF 快。

需要提醒的是,AOF 开 always 的情况下,单实例写吞吐会掉得厉害,而且 fsync 是同步磁盘 I/O,高并发写别轻易上 always。多数情况 everysec + 混合持久化已经够。

真出事时,怎么恢复

Redis 重启会自己找持久化文件加载,顺序是:先 AOF(若开启),否则 RDB。加载过程写在日志里,出问题先看日志。

文件损坏是另一种噩梦。AOF 因为是追加写,异常退出可能留下半截命令;RDB 因为带 CRC,损坏会被校验发现。两者都有官方修复工具:

# 修复 AOF:把文件截断到最后一个完整合法的命令,丢掉尾部残段
redis-check-aof --fix appendonly.aof.1.incr.aof

# 修复 RDB:校验并尽可能恢复(RDB 是整体快照,损坏通常只能丢弃整个文件)
redis-check-rdb dump.rdb

redis-check-aof --fix 是相对安全的操作——它只切掉末尾损坏部分,前面完整命令都在。--fix 前一定先拷一份原文件再修,别直接原地改。

AOF 文件结构从 Redis 4.0 起是多文件appendonly.aof.1.base.rdb(基础 RDB)、appendonly.aof.1.incr.aof(增量)、appendonly.aof.manifest(清单)。修的时候要对 incr 那个文件用 redis-check-aof,别拿 base 去修。

还有个救命场景:误执行了 FLUSHALL / FLUSHDB。这时候千万别让 Redis 自己退出重建 AOF——赶紧 SHUTDOWN NOSAVE(不落盘直接关),然后用备份的 AOF/RDB 把数据换回去再起。一旦它正常 BGREWRITEAOF 把清空也写进 AOF,数据就真没了。这条我记在脑子里当作「黄金三分钟」:误操作后第一反应是停写、别让重写发生。

主从、云托管那些额外的坑

主从架构下,从库默认会跟着主库的重写节奏走,但真正要担心的是主从切换时的数据丢失。Redis 异步复制,主库挂掉的瞬间,还没同步给从库的那部分写就没了。可以用:

min-replicas-to-write 1        # 至少要有 1 个从库落后不超过下方阈值才接受写
min-replicas-max-lag 10        # 从库延迟超过 10 秒,主库停止写入(牺牲可用性换不丢)

这是用「写入可用性」换「不丢数据」的硬权衡,得想清楚业务能不能接受主库在从库抖动时拒写。

云上的托管 Redis(阿里云、腾讯云那些)通常把持久化策略藏在自己控制台后面,表面「高可用」不等于「不丢」。做过一次故障演练就知道:某云 Redis 主节点故障切换后, reconnect 上来的客户端短暂写进了一个空实例——因为新主是从一个稍旧的副本起来的。关键业务别把 Redis 当唯一数据源,它和后端 DB 之间得有「以 DB 为准、Redis 只是加速层」的清醒认知。

运维平时该盯什么

几个 info persistence 里的字段,监控和巡检时值的看:

redis-cli info persistence | grep -E \
  '^(aof_enabled|aof_last_bgrewrite_status|aof_current_size|rdb_last_bgsave_status|loading):'
  • rdb_last_bgsave_status:不是 ok 就要查(磁盘满?权限?fork 失败?)。
  • aof_last_bgrewrite_status:重写是否成功,连续失败说明磁盘或内存有问题。
  • aof_current_size / aof_base_size:看 AOF 膨胀速度,逼近重写阈值前心里有数。
  • loading:1:表示正在加载持久化文件,期间实例不对外服务,加载慢(大 AOF)会拉长故障恢复时间。

备份策略上,别只靠本机 AOF。我习惯每天业务低峰 BGSAVE 一次,然后把 dump.rdb 连同 appendonly.aof.* 打包丢到对象存储异地留存。Redis 机器本地磁盘坏了,你至少有昨天那份能回。云上就直接打快照,道理一样。

收个尾

Redis 持久化没有「开了就万事大吉」。RDB 简单快但留时间窗,AOF 稳但要管膨胀和重写,混合持久化是当下最省心的默认。真到了恢复现场,先分清是 AOF 尾部损坏(能 --fix 截断救)还是整体崩坏(靠备份换),FLUSHALL 之后那几分钟别让重写发生就是救命。把它当加速层而不是唯一真相源,很多Redis 数据事故的焦虑,其实从架构上就消解了。

分享:

相关文章

缓存三连坑:穿透、击穿、雪崩,以及它们真的发生时你该做什么
数据库运维12 min read

缓存三连坑:穿透、击穿、雪崩,以及它们真的发生时你该做什么

早上十点,监控群炸了:订单库 CPU 直接 100%,连接池打满,前端大批超时。Redis 这边呢,命中率从平时的 98% 掉到了 20% 出头。重启应用没用,把流量砍一半也没用,DB 还是在喘。最后定位到根因——前一晚发版时,一批缓存 key 被统一刷新,TTL 都设成了 3600 秒,于是这一

PG 大版本不能原地升:用 pg_upgrade 把 12 升到 16,停机从小时级压到分钟级
数据库运维13 min read

PG 大版本不能原地升:用 pg_upgrade 把 12 升到 16,停机从小时级压到分钟级

PostgreSQL 的大版本(10→12→15→16)catalog 不兼容,没法像小版本那样 yum update 后直接起。早年大家靠 pg_dump 导出再导入,几百 G 的库停机大半天。pg_upgrade 走的是「复用旧数据文件」的路子,copy 模式原样搬、link 模式直接建硬链接,停机往往压到几分钟。但它不是傻瓜命令——扩展版本不对、locale 不一致、表空间映射错一点,升级就中途报错回退。

评论区