
聊到Redis持久化机制RDB和AOF的区别基本是每个玩Redis的人都绕不开的问题。这问题看着像面试题实际上它直接决定了你的Redis在宕机重启之后能找回多少数据。我第一次认真研究这块是因为线上出了事故一个业务节点的Redis运行了几个月一切正常结果机房断电重启后缓存层近一半的热点数据直接没了。排查下来那台实例只开了RDB快照间隔配置又比较大断电前最后一个快照之后产生的写入全部人间蒸发。从那次之后我把RDB和AOF从头到尾理了一遍才发现理解它们的关键根本不是背参数而是搞清楚“快照”和“日志”这两条完全不同的路到底在取舍什么。这篇文章写给两类人一是面试前想把这个经典问题讲清楚的程序员二是正在搭建Redis生产环境、纠结持久化方案怎么选的后端同学。我会从原理、配置、真实故障三个层面把两套机制彻底拆开最后给出我目前在用的生产配置方便直接参考。1. 先把问题问对Redis为什么需要两套持久化方案1.1 默认情况下Redis有多“脆”Redis是典型的内存型数据库所有读写都发生在内存里所以速度极快能扛住高并发。但内存里的一切本质上是活在RAM上的节点断电、进程崩溃、OOM被系统杀掉数据都可能瞬间物理归零。注意我说的是“物理归零”不是假死如果没有缓存预热机制业务层很可能直接雪崩。很多人第一反应是“Redis不是有主从复制吗从节点顶上不就完了”。主从复制解决的是“多副本”问题节点挂了从节点能接管但复制本质上还是基于内存数据同步。如果整个集群同时重启且没有任何落盘的持久化副本数据依然回不来。真正让数据“从内存里走出来”的是持久化机制。Redis官方提供了两套持久化落地方案RDB和AOF。RDBRedis Database Backup对内存数据集做二进制快照存的是“最终长什么样”。AOFAppend Only File把每一条写命令追加到日志文件存的是“都做了哪些操作”。一个是存结果一个是存过程。评论区经常有人纠结“哪个好”其实不存在绝对的好只有适不适合你的业务对数据丢失的容忍度。1.2 两种思路游戏存档 和 操作录屏如果想找个生活化类比我的理解是RDB相当于游戏里的“存档”。你玩到某个进度把当前状态保存下来下次读档直接回到那个时间点速度快但存档之后你又玩了几十分钟如果没再存档这部分进度就没了。AOF相当于“录屏重放”。你把每步操作都记录下来恢复时从头重新播放一遍可以精确还原到最后一帧但录屏文件会越来越大恢复时间也更长。这两种思路指向完全不同的取舍。RDB关心“我最终长什么样”AOF关心“我都干过什么”。后面的一切差异包括恢复速度、文件体积、丢失风险、磁盘占用、性能损耗全都是从“存结果还是存过程”这个源头推导出来的。我建议你先在自己本机做个直观验证启动一个不配任何持久化的Redis实例set一批key然后重启进程执行dbsize大概率返回0。这个体验非常刺激能让你立刻理解持久化为什么是刚需。2. RDB快照把整个数据集拍成一张照片2.1 RDB的触发方式比你想的多RDB的实质很简单把当前内存中全量数据序列化成一个紧凑的二进制文件默认文件名是dump.rdb存放目录由dir配置指定。恢复时把文件一次性读回内存。触发RDB的方式有下面几种很多教程只会提自动触发容易让人忽略其他入口手动执行save命令同步全量写盘期间Redis主线程完全阻塞写盘越大卡顿越久生产环境基本不这么干。手动执行bgsave命令后台保存fork一个子进程去写RDB文件父进程继续处理请求。这是核心机制我下面单独讲。按配置规则自动触发redis.conf里的save 满足“N秒内至少有M次写操作”就自动执行一次bgsave。正常shutdown时触发Redis以正常方式关闭时如果RDB开关开着会主动保存一次快照再退出。注意kill -9直接杀进程不会触发这个逻辑。主从复制全量同步时master会把当前RDB文件连带后续增量发给slave这也是RDB的常见用途之一。2.2 bgsave靠fork子进程干活别再以为快照会锁死服务bgsave是RDB的核心也是面试官最爱的追问点。它执行时会调用fork创建一个子进程子进程去写RDB文件。这里的关键是fork出来的子进程并不需要完整复制一份父进程的内存。操作系统提供了copy-on-writeCOW机制fork瞬间父子进程共享同一批物理内存页谁改动了某个页这个页才会被复制一份出来。对Redis而言子进程在快照期间遍历内存、写文件读到的一直是fork那一刻的数据集合父进程继续处理请求如果内存页被修改就触发页复制把改动落到自己的副本上。这个设计代价有两点fork瞬间有短暂停顿要拷贝页表等元数据实例内存越大停顿越明显。COW带来的额外内存和CPU开销极端情况下可能让系统内存翻倍增长。所以bgsave不是完全没有阻塞成本只是把“长时间写入盘阻塞”换成了“短暂fork阻塞后台写盘”。我测过几十GB的实例fork卡顿从几十毫秒到几百毫秒都可能内存越是吃紧越明显。这也是为什么生产环境要控制大key数量避免bgsave期间COW压力太猛。2.3 核心配置一眼看懂以一段常见配置为例save 3600 1 save 300 100 save 60 10000 dbfilename dump.rdb dir /data/redis这里有三条触发规则含义分别是3600秒1小时内至少有1次写操作触发一次快照。300秒5分钟内至少有100次写操作触发一次快照。60秒1分钟内至少有10000次写操作触发一次快照。为什么要设计多条规则这是个细节。Redis的初衷是怕极端场景下写频率太低导致长时间没有快照同时如果写频率爆炸高又能用较短间隔兜底。比如一个低流量业务一小时只写了两次数据如果只配“60秒10000次”的规则那它可能永远不触发快照而“3600秒1次”就保证了至少每小时有一份存档。2.4 RDB的优势和代价优点文件紧凑磁盘占用小适合做全量备份和灾备归档。恢复速度极快直接加载二进制快照不需要重放命令。bgsave对父进程影响相对可控只要不频繁触发一般不产生持续性能损耗。代价数据丢失窗口大。两次快照之间的所有写入一旦崩溃全部丢失。save 900 1意味着最坏情况丢15分钟数据这对很多业务都是不可接受的。频繁bgsave会带来额外磁盘IO和fork开销。全量快照是遍历整个数据集实例越大写盘耗时越长COW风险越高。可以这么说RDB适合“丢了最近一部分数据可以接受、但恢复必须快”的场景。它天然适合做每日备份归档但不适合单独承担高完整性要求的存储职责。3. AOF追加日志把每条写命令记成一本流水账3.1 存的是命令不是结果AOF启用后Redis会把收到的每一条写命令追加到aof文件。因为存的是命令本身恢复时就等同于把命令重新执行一遍。文件内容是RESP协议文本看起来类似这样*3 $3 SET $3 key $5 value加上回车换行后就是*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n这个格式的优点是透明可读程序员至少能看出这是什么操作也方便做人工审计。文件损坏时可以用工具尽量修复。AOF默认是关闭的需要在redis.conf里设置appendonly yes才能开启。开启后出现的文件默认叫appendonly.aof。AOF的丢失窗口比RDB小很多理论上可以做到最多只丢一条命令比RDB的“秒级窗口”精细不少。代价是每一笔写操作都要承担追加日志的开销。3.2 appendfsync三个级别可靠的代价是性能有个细节很多新手容易忽略写命令并不是立刻“落盘”的而是先写入操作系统缓冲区page cache真正写到磁盘是由fsync触发的。操作系统什么时候把缓冲刷到磁盘本来是个黑盒Redis给了你三个手动控制级别级别行为可靠性性能影响always每次写命令后立即fsync最安全最多丢一条命令最慢QPS会明显掉everysec每秒执行一次fsync最多丢1秒数据推荐性能损耗可接受no由操作系统决定何时落盘不可控最快但最不保险everysec是生产里最常见的设置。它把“最多丢1秒数据”作为默认折中绝大多数业务都能接受。always虽然听起来最完美但试过就知道每一次写命令都卡一次fsync高并发下性能损耗肉眼可见我甚至见过配置always后同一接口的P99直接翻倍的情况。no这个级别适合那些对丢数据无所谓、只想极限压性能的场景一般不建议。3.3 AOF重写不让流水账无限膨胀AOF最大的问题是文件会持续膨胀。同一个key反复被set 100次RDB只留下最终值而AOF会把100条命令全记下来。日志越写越长文件体积越来越大将来恢复时就要重放更多命令启动时间越拖越慢。解决办法叫AOF重写由bgrewriteaof命令或自动配置触发。它做的事情是fork子进程扫描当前内存数据集生成一组精简指令相当于把所有历史操作“压缩”成最新状态。比如一个key被赋值100遍重写后只保留最后一次SET。重写期间还有一个细节父进程新接收的写命令不会丢它们会被写入AOF缓冲区等重写完成后追加到新文件末尾然后原子替换旧文件保证数据和日志一致。Redis 7.0之后AOF文件结构做了一次调整变成了base file加incr file的多文件形式重写时生成一个新的base文件重写后的增量命令继续追加到incr文件。加载时先读base再读incr。这个改动让AOF能更好地支持后台重写和快速重启。3.4 AOF的优点和代价优点恢复粒度细everysec下最多丢1秒数据always下最多丢一条命令。日志可读能做人工干预和分析。相比RDB更适合对数据完整性有较高要求的业务。代价文件体积大通常明显大于RDB。恢复速度比RDB慢因为需要把命令逐条重放一遍。一个10GB的AOF文件恢复可能要几分钟甚至更久而同样数据量的RDB一般几十秒内就能加载完。写放大明显每次写命令都要追加日志always策略下额外叠加fsync开销。顺便说个实操感受如果你从备份里恢复一个Redis实例时间窗口本身也是重要的排查维度。AOF重放耗时太长时业务等不起这时候混合方案的优势就体现出来了下一节细讲。4. 逐项对比安全性、恢复速度、磁盘开销、性能影响4.1 一张表看清核心差异我整理了下面这张对比表面试前背这一张基本够用对比维度RDBAOF记录内容全量内存数据的二进制快照每一条写命令的日志默认状态默认开启默认关闭数据丢失窗口两次快照之间的数据可能全丢由fsync策略决定everysec最多丢1秒恢复速度快直接加载快照慢需要重放命令文件体积紧凑通常更大写入性能影响快照间隙无影响bgsave和fork有短暂开销每次写命令都涉及日志追加always损耗最大可读性二进制基本不可读文本协议可读可审计容错与修复文件损坏后基本依赖备份可用redis-check-aof对轻度损坏做修复适用场景备份归档、全量同步、恢复速度优先数据完整性要求高、丢失容忍度低4.2 从业务场景反推选型结合我遇到的实际情况给出三个比较典型的选型方向纯缓存、数据可重建比如商品详情缓存、配置缓存数据源头在数据库Redis丢了最多回源重查。这种场景可以只开RDB做兜底甚至两者都关都能接受具体看你对突发流量回源数据库的承担能力。存了业务状态数据比如排行榜、会话状态、订单中间态。这种数据丢了很麻烦AOF everysec必须开同时保留RDB规则做每日全量备份。想做准可靠性存储即便配了always我仍然不建议把Redis当成唯一数据库用。Redis的定位是高性能缓存和存储组件关键交易数据最终应该落进真正的数据库Redis持久化只是降低丢失风险不是替代数据库的事务保证。4.3 几个容易搞错的认知第一别以为RDB一定比AOF“快”。RDB的优势在恢复速度不是写入速度。AOF的写入开销和文件膨胀问题反而更需要重视。第二开了AOF不代表RDB就不需要。RDB作为备份归档、传给从节点全量同步依然有不可替代的价值。两者不是互斥关系是互补关系。第三AOF文件不会一直小而美长期不重写会让它膨胀到很可怕。你要监控auto-aof-rewrite-percentage和auto-aof-rewrite-min-size确保重写在合理时机发生。5. 混合持久化Redis 4.0之后的最优解5.1 为什么要混合各取所长4.0版本之前开发者会面临两难RDB恢复快但丢数据多AOF丢数据少但恢复慢。生产环境经常希望两者优点兼得于是Redis从4.0开始提供混合持久化方案。核心思路是在AOF文件里嵌入RDB格式的数据前缀。AOF重写时不再先写一大串SET命令而是先以RDB二进制格式把当前数据集写入文件再在此之后追加重写期间产生的新写命令。这样一份文件里既有RDB快照的紧凑和快速加载特性又有AOF命令的实时恢复能力。5.2 恢复过程是怎么运作的启动加载这个混合AOF文件时Redis先快速加载文件前部的RDB数据段把大部分内存数据直接恢复出来然后再重放末尾的增量命令把最后一段时间的新写补上。实测体验差距很大一个几十GB的实例纯AOF文件重放非常吃力启动可能要几分钟期间业务基本是黑的混合模式下前半段由RDB加载后面增量命令往往只有很小一段整体启动时间能缩短一大截。这个优势在大型实例上特别明显。5.3 配置建议与版本差异在redis.conf里这样配appendonly yes appendfsync everysec aof-use-rdb-preamble yes auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb需要注意版本差异Redis 4.0引入了aof-use-rdb-preamble配置项默认no需要手动开启。Redis 5.0开始默认就是yes所以如果你用5.0以上版本只需要打开AOF混合持久化默认生效。另外提醒一句开启混合持久化后AOF文件不再是纯文本开头是RDB二进制段所以不能直接用编辑器查看到全部命令。遇到需要定位日志内容的场景先用redis-check-aof做检查。6. 从持久化到生产实践几个值得记住的故障与细节6.1 启动时的加载顺序搞反容易被坑如果AOF开启且文件存在Redis启动时会优先加载AOF文件而不是RDB文件AOF关闭或者文件不存在时才回去加载RDB文件。这个顺序背后的逻辑是AOF表达了更细粒度的写操作数据的完整程度通常更高所以被设计成优先选择。但这里有个容易被坑的点假如你开了AOF文件恰好损坏Redis可能拒绝启动或者一直报错。而如果你同时保留了RDB备份想“靠RDB兜底”得先把AOF文件移走或修复再让Redis启动时走RDB加载路线。这个流程我在测试环境经历过一次很别扭建议提前把修复工具的用法记牢。6.2 故障实例AOF文件损坏怎么救我曾经在一次异常断电后发现Redis进程起不来日志里刷屏“Bad file format reading the append only file”。原因基本就是AOF文件末尾被截断最后几条写入没落完整。修复步骤很简单cp appendonly.aof appendonly.aof.bak redis-check-aof --fix appendonly.aof工具会扫描AOF文件把末尾不完整的命令截掉修复后Redis可以正常启动。注意被截掉的写命令本身还是丢了这是只能修复文件格式、无法找回内容的现实。所以我一直强调AOF能降低丢失窗口但“RDB备份 AOF实时”的组合才是真保险。6.3 fork耗时混合方案也不是随便乱配RDB和AOF重写都依赖fork子进程而fork本身要拷贝页表元数据。当Redis实例占用内存几十GB、又赶上服务器内存紧张时fork过程可能产生几百毫秒甚至秒级阻塞。这个阻塞不会因为你开的是混合模式就自动消失。排查命令很简单redis-cli info stats | grep fork重点关注latest_fork_usec这是最近一次fork消耗的微秒数。如果数据明显偏大你就要考虑是不是实例内存过大、系统内存不足或者快照触发得过于频繁。常见的优化思路是把自动触发规则放宽松把bgsave和bgrewriteaof的时点挪到低峰期比如凌晨固定跑一次。6.4 主从复制和持久化它们怎么配合兜底主从复制和持久化是两回事但经常被混在一起。主从全量同步时master会生成RDB发给slaveslave清空旧数据再加载新数据集。这里说的RDB是复制链路上的传输载体和你在master上手动配的持久化规则不完全是一码事。如果master自身内存全丢持久化文件也丢那slave再怎么切换也找不到数据源头。所以行业里的常规做法是master开启持久化slave按需开启同时在低峰期做每日RDB异地备份保留N天。我公司里的做法是写个crontab凌晨让主实例执行一次bgsave后把dump.rdb同步到对象存储保留7天留作灾难恢复的最后一道防线。如果你要我给一个直接能用的生产配置我会这么写appendonly yes appendfsync everysec aof-use-rdb-preamble yes save 3600 1 save 300 100 save 60 10000这套组合兼顾了实时性、恢复速度和可操作性。大部分业务场景下它比我见过的各种“极致调参”要稳得多。真到了连这套配置都觉得不够用的时候问题多半不在持久化本身而在于你该考虑更可靠的存储方案了。