Redis持久化详解:RDB快照与AOF日志的原理、配置与排障
去年有次线上值班早上六点被人从被窝里叫醒现象是“Redis重启后数据全部消失”。当时查了一圈不是主从切换、不是内存淘汰、也不是谁手滑执行了 FLUSHALL最后才发现是新部署的节点上持久化根本没开启。从那以后我养成了一个习惯任何 Redis 节点上线第一件事先确认 RDB 和 AOF 到底开没开。这篇文章说的正是 Redis 持久化的两条主流路线——RDB 快照和 AOF 日志。RDB 是在固定时间点给全量数据拍一张二进制照片AOF 则是把每次写命令按 Redis 协议格式追加进日志。这篇内容不只讲它俩的区别也把原理、配置、真实环境里容易踩的坑一起过一遍适合刚入门想系统理解持久化的同学也适合被“重启后数据全没了”折磨过的后端和运维朋友。1. RDB与AOF的核心定位与整体设计思路1.1 为什么Redis要同时维护两套持久化方案先理清背景Redis 的数据默认都活在内存里读写性能极好但代价是进程退出、主机宕机、断电都可能导致数据全部蒸发。早期 Redis 只提供 RDB 持久化把某个时间点的完整数据集导出成一份二进制文件。RDB 的优点是文件紧凑、恢复速度快适合做冷备和快速回滚缺点是快照之间有固定间隔比如你配置 60 秒存一次那最后 60 秒内的全部写入在极端情况下说丢就丢。很多业务受不了这个丢失窗口于是后来引入了 AOF。AOF 的思路和数据库常见的 WAL 类似不保存“某个时刻的数据长什么样”而是把每一条写命令本身记录下来重启时把命令一条条重放从而重建最终数据。你可以用拍照和记账来区分RDB 是每隔一段时间拍一张全家福AOF 是每个动作都写进账本。两种方案的定位完全不同RDB 更适合备份、主从同步初始化、快速恢复AOF 更适合把数据丢失窗口压到秒级甚至单条命令。实际生产里它们不是“二选一”的竞争关系而是互补关系我后面会说具体怎么组合。1.2 RDB和AOF的关键差异一眼看明白先给一张对比表把最核心的区别摆出来。对比项RDB 快照AOF 日志数据形态二进制快照文件内容是序列化后的全量数据集Redis 协议格式的写命令序列文本可读生成方式定期、手动执行 SAVE/BGSAVE 生成每次写命令进入内存缓冲区再按策略写入磁盘恢复速度快直接加载数据文件相对慢启动时要逐条重放命令数据安全性可能丢失上一次快照后的全部改动取决于 appendfsync 策略最小可做到每命令同步文件体积紧凑、明显更小通常较大但可定期重写压缩对正常读写影响BGSAVE 通过 fork 子进程影响较小everysec 影响小always 写入开销大典型场景冷备、快速恢复、主从全量同步数据安全要求高的主实例这里有个很多文档写得不清楚的细节当 RDB 和 AOF 同时开启时Redis 启动会优先加载 AOF而不是 RDB。原因很简单AOF 的记录粒度更细理论上包含更多最新数据Redis 自然选择“信息更全”的那份文件。所以双开时AOF 文件必须纳入备份体系别以为有 RDB 兜底就不用管 AOF。2. 核心细节解析快照与日志的底层机制2.1 RDB快照的生成过程与写时复制原理RDB 生成的核心机制可以概括为 fork 写时复制Copy On WriteCOW。主进程执行 BGSAVE 时会通过 fork 创建一个子进程子进程继承父进程的页表然后子进程负责把内存里的数据集写入临时 RDB 文件写完后通过 rename 原子替换成最终文件名。这样即使生成过程崩溃也不会留下一个写了一半的脏文件直接被加载。关键点是fork 出来的子进程看到的内存页是 fork 那一刻的快照。之后主进程仍然继续接受新的读写命令如果这些新写入修改了某个内存页操作系统会把这个页复制一份给父进程自己用子进程继续使用原来的旧页。所以快照本质上是一个“一致性视图”不阻塞主流程。这也是为什么 BGSAVE 对大实例看起来“不卡”但实际上有两处隐藏代价一是 fork 瞬间要复制页表实例越大阻塞时间相对越长虽然通常只有几毫秒到几十毫秒二是写时复制期间如果写入量很大内存页会被大量复制极端情况下内存使用量可能接近原有数据的两倍内存小的机器容易被 OOM。Linux 上还建议把vm.overcommit_memory设置为 1不然 fork 可能因为内存预分配策略失败。除了手动执行 SAVE 和 BGSAVERDB 常见的触发场景包括满足配置的快照条件、主从全量同步、执行 SHUTDOWN 或 DEBUG RELOAD。注意 SAVE 是同步阻塞的生产环境几乎不用BGSAVE 是异步的只在 fork 瞬间阻塞。自动快照条件由save参数控制比如下面三行配置表示900 秒内有 1 次写操作、300 秒内有 10 次写操作、60 秒内有 10000 次写操作满足任意一个就触发 BGSAVE。多行配置是“或”的关系条件设得越密集数据丢失窗口越小但磁盘写压力和 fork 频率也越高需要自己权衡。2.2 AOF日志的写入链路、刷盘策略与自动重写AOF 的写入链路可以拆成四步Redis 执行写命令后把命令内容按协议格式追加到内存里的aof_buf在合适的事件循环时机调用write()把数据写入操作系统的内核缓冲区真正的落盘由fsync控制fsync的执行频率由appendfsync参数决定。很多人把write()当成落盘其实不是write()只是把数据交到内核手里断电时内核缓冲区的数据照样会丢。appendfsync有三种策略区别非常明显always每条写命令都执行fsync安全级别最高最多可能丢一条命令但写入吞吐会明显下降机械盘或者网络存储上尤其夸张。everysec由后台线程每秒执行一次fsync极端情况下最多丢最后一秒数据这是生产环境最常见的折中方案。no不主动fsync完全交给系统内核自行刷盘性能最好但崩溃时可能丢更多数据。AOF 文件还有一个绕不开的问题会一直膨胀。比如对一个 key 执行一万次 INCRAOF 里就会有一万条 INCR 记录真正恢复时只需要一条 SET 就能表达最终结果。于是 Redis 提供了重写机制BGREWRITEAOF会 fork 出一个子进程扫描当前内存中的完整数据集生成最小的命令序列写入临时 AOF 文件重写期间产生的新命令会被放进重写缓冲区最后一起追加并原子替换旧文件。自动重写的触发条件由auto-aof-rewrite-percentage 100和auto-aof-rewrite-min-size 64mb控制意思是 AOF 体积比上次重写后增长了 100%且绝对大小超过 64MB 时才会重写。这个设计是为了避免频繁重写浪费资源。2.3 数据安全、恢复速度与混合持久化的取舍从机制上就不难理解RDB 和 AOF 的优势是互斥的RDB 恢复快、文件小但丢失窗口取决于快照间隔AOF 能精确到秒级甚至命令级但文件大、重放慢。为了同时拿到两边的优点Redis 4.0 引入了混合持久化。开启aof-use-rdb-preamble yes后AOF 重写生成的日志文件前面会直接以 RDB 二进制格式写入当前全量数据后面再追加重写期间产生的少量增量命令。加载的时候先快速加载 RDB 部分再重放少量命令数据安全性和恢复速度都有提升。判断一个 AOF 文件是不是混合格式很简单用head -c 5 appendonly.aof查看如果开头是REDIS字样就说明带 RDB 前缀。Redis 5、6、7 的默认配置里基本都开了这个选项所以你在生产环境看到的大多数 AOF 文件已经不是纯文本命令了没必要惊讶。3. 实操过程配置、选型与备份恢复3.1 RDB配置参数怎么调才不出错先给一份可以直接参考的 RDB 配置片段# 自动快照触发条件满足任一条件就执行 BGSAVE save 3600 1 save 300 100 save 60 10000 # 快照文件名和目录 dbfilename dump.rdb dir /var/lib/redis # BGSAVE 失败时是否停止写入 stop-writes-on-bgsave-error yes # 快照文件是否压缩、是否做 CRC64 校验 rdbcompression yes rdbchecksum yesdir这个参数特别容易被忽略。很多人只在配置里写了dbfilename却没注意 Redis 的实际工作目录导致启动后找不到之前的dump.rdb。用 systemd 启动时工作目录可能是/或/var/lib/redis和你手动执行 redis-server 的目录不一定一样。建议每次部署后执行config get dir确认路径。stop-writes-on-bgsave-error yes的意思是磁盘出现故障、快照无法生成时Redis 会主动拒绝写命令这个行为乍看有点激进但能避免“客户端以为写入成功、重启后数据全没”的恶性事故线上环境建议保持开启。rdbchecksum会带来少量读写开销但能识别文件损坏除非你对性能极度敏感否则不建议关闭。判断 RDB 是否正常可以用info persistence看rdb_last_bgsave_status和rdb_bgsave_in_progress。如果你看到rdb_last_bgsave_status:err那就要立刻查磁盘因为说明上一次快照已经失败了。3.2 AOF配置参数与混合持久化的正确开启方式AOF 相关配置我一般这样写appendonly yes appendfilename appendonly.aof appendfsync everysec # 重写期间是否照常 fsync no-appendfsync-on-rewrite no # 自动重写触发条件 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # 允许加载截断的 AOF 文件 aof-load-truncated yes # 混合持久化 aof-use-rdb-preamble yesappendfsync我几乎不会在生产环境开always除非业务对数据一致性有硬性要求。因为always对吞吐影响太大了在大量写入场景下会直接把 Redis 的写性能拉低一个量级。everysec丢一秒数据的窗口对绝大多数业务都可以接受这也是 Redis 官方文档里推荐的折中。no-appendfsync-on-rewrite控制的是重写期间是否暂停fsync默认no会有更好的安全性但重写时磁盘压力变大。如果你的写入量很大AOF 重写又频繁可以考虑临时调整。还有一个经常坑人的场景从“只开 RDB”切换到“开启 AOF”。很多人用config set appendonly yes在线开启看到 AOF 文件生成了就以为完事结果重启后配置又回到no最后还是加载 RDB甚至因为 RDB 没更新而丢数据。正确做法是热切换后立刻执行config rewrite把运行参数固化到配置文件里同时手工检查redis.conf里确实已经变成appendonly yes。热切换本身不会丢失原有数据因为 Redis 会基于当前内存状态生成初始 AOF 基底关键是后续重启时配置不能漂。3.3 不同业务场景下RDB和AOF怎么选型选型没有一个万能答案只能按业务对数据丢失的容忍度来分。业务场景持久化建议原因纯缓存缓存可重建可完全不持久化或只开 RDB丢数据影响小追求性能和运维简单登录态、用户会话、购物车AOF everysec RDB允许极小概率丢一秒但重启能快速恢复订单、积分、分布式锁AOF everysec 或 always RDB丢失记录会造成资损或并发安全问题需更高防护从节点、只读副本通常关闭持久化或只开 RDB数据由主节点下发重点承担读流量容灾冷备周期性 RDB 文件异地备份RDB 文件小、加载快适合归档特别提醒一点使用 Redis 做分布式锁时持久化配置不能想当然。如果主库重启导致锁记录丢失所有客户端可能同时认为自己持有锁进而产生并发安全问题。所以锁服务所在实例建议至少开启 AOF everysec条件允许时再配合主从和哨兵。这也是现在很多 Redis 面试题不断追问持久化的原因它直接关系到分布式环境下的一致性表现。容器环境里还有三个额外的坑要避开数据目录必须挂载到持久化卷否则容器重建等于销毁文件启动命令里要显式指定redis.conf很多镜像默认不加载外部配置停止容器时不要用强杀要留足够宽限期让 Redis 正常退出并处理关闭流程。3.4 一份可直接落地的持久化恢复流程这里给一套我实际用过的恢复流程操作顺序非常关键先停掉业务写入或者直接下线实例避免恢复过程中产生脏数据。用info persistence确认当前持久化开关和文件路径定位 RDB/AOF 文件到底在哪。先用redis-check-rdb和redis-check-aof检查文件完整性不要直接启动 Redis否则可能加载损坏文件后继续工作把问题扩大。备份一份原地文件再做任何修复操作。修复损坏文件确认无误后启动 Redis观察启动日志确认加载了多少数据。用客户端抽查几个关键 key确认数据量级与预期一致再恢复业务流量。这套流程看起来平淡但能避免“启动成功但数据不对”的二次事故。尤其检查文件完整性这一步很多人会跳过真实生产里我见过 AOF 文件损坏后 Redis 拒绝启动然后有人直接删除 AOF 文件重启的等于是主动选择了从零开始。4. 常见问题与排查技巧实录4.1 重启后数据丢了优先查这几个地方“Redis 重启后数据全丢”是我被问得最多的一个问题排查顺序一般是这样持久化开关真的开了吗有的实例启动命令没有指定配置文件或者 systemd 的 ExecStart 覆盖了配置导致save被注释、appendonly no看起来是 Redis 在运行实际没有任何持久化动作。配置文件和工作目录对不对最常见的是dir路径不一致Redis 启动后找不到旧的dump.rdb或appendonly.aof自然恢复成空库。磁盘和权限正常吗磁盘满了或者文件写不进去RDB 持久化失败后stop-writes-on-bgsave-error可能已经让 Redis 停止写入了但业务侧可能只看到“写入超时”不会马上联想到持久化问题。容器场景挂载了吗Docker 里没挂数据卷容器重建后数据文件直接没了这种情况和 Redis 本身关系不大但最容易让新手误判。我遇到过最隐蔽的一次是AOF 确实开了但只是config set appendonly yes在线生效配置文件一直没更新。之后有人重启了 Redis配置回退重启恢复的是旧 RDB数据直接倒退了一个晚上。4.2 RDB/AOF文件损坏了怎么修Redis 启动时如果发现文件损坏日志里通常会出现Wrong signature trying to load RDB file或Bad file format之类的信息。RDB 文件用redis-check-rdb检查AOF 文件用redis-check-aof修复命令大概是# 检查 RDB 文件 redis-check-rdb /var/lib/redis/dump.rdb # 修复 AOF 文件--fix 会去掉尾部损坏的无效命令 redis-check-aof --fix /var/lib/redis/appendonly.aofAOF 修复的本质是“截断”会把无法解析的尾部命令丢掉所以修复后确实会损失崩溃瞬间附近的一点数据但至少能保住前面绝大部分内容。执行修复前一定先备份原文件我习惯把损坏文件复制成appendonly.aof.bak再动手。另外Redis 提供了aof-load-truncated yes配置如果 AOF 只是末尾被截断而非中间损坏Redis 会允许加载并打日志警告这个参数默认开启不建议关掉但要在监控里把相关日志拉出来人工确认一下。4.3 fork耗时和AOF重写导致的性能抖动怎么处理持久化影响性能主要有两个场景BGSAVE 的 fork 阻塞以及 AOF 重写期间的磁盘竞争。排查 fork 耗时用info stats里的latest_fork_usec这个值如果持续飙升到几百毫秒甚至秒级说明实例很大或者机器内存压力很高。此时可以在业务低峰期手动执行 BGSAVE错开高峰也可以把 RDB 备份任务放到从节点去做主节点专心服务读写。AOF 重写的自动触发条件不好精确控制如果业务高峰时正好撞上重写会出现短暂延迟。我的做法是在低峰期手动执行BGREWRITEAOF把重写时间控在自己可控范围内并把auto-aof-rewrite-percentage调大一些降低自动重写的频率。另外如果 RDB 和 AOF 落在同一块磁盘上BGSAVE 和 AOF 刷盘会互相争抢 IO条件允许时把它们分到不同物理盘或云盘上能明显缓解抖动。4.4 版本兼容与加载顺序那些容易忽略的坑RDB 文件存在版本兼容问题老版本 Redis 可能无法加载新版本生成的 RDB。升级 Redis 版本后如果日志报类似Cant handle RDB format version的错误说明新文件格式旧进程读不了。升级前最好先停服手动触发一次 BGSAVE确认能恢复再跑版本升级。AOF 命令格式相对稳定但如果你跨大版本升级也不能完全赌它不出问题稳妥做法是先备份 AOF 再升级。关于加载顺序的坑我再强调一次同时开启 RDB 和 AOF 时Redis 启动优先加载 AOF。有些同学认为“我有 RDBAOF 丢了不慌”结果 AOF 文件损坏或缺失时启动加载可能失败或者恢复出异常数据。所以双开模式下AOF 文件和 RDB 一样重要都要纳入备份和巡检范围。4.5 面试里怎么把RDB和AOF讲清楚面试题如果问到持久化一个清晰的回答结构大概是先定义——RDB 是定期生成的二进制快照AOF 是追加式命令日志再说优缺点——RDB 恢复快、文件小但丢失窗口大AOF 丢失窗口小但文件大、恢复慢然后提到机制——BGSAVE 依赖 fork 和写时复制AOF 靠appendfsync控制刷盘体积膨胀靠BGREWRITEAOF解决最后说生产实践——双开开启AOF everysec 为主RDB 做冷备和兜底Redis 4.0 之后开混合持久化启动加载时优先 AOF。这样答既有深度又能落到工程实践基本不会冷场。我个人在实际维护中的默认组合是Redis 7 AOF everysec RDB 兜底 混合持久化RDB 文件周期性异地备份。AOF 负责把崩溃丢失控制在一秒内RDB 负责快速冷备和主从初始化两者关掉任何一个我都会觉得心里没底。最后再分享一个小技巧每次维护重启前执行一次config rewrite把当前有效参数固化到文件里再配合info persistence查看 RDB/AOF 状态能避开不少“重启完配置漂移”的低级问题。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →