
Redis这玩意儿平时跑得欢可一旦宕机数据说没就没。我之前遇到过一个挺典型的案例凌晨两点业务方电话打过来说一台Redis实例重启后登录态丢了大半。上去一看RDB文件的修改时间还停在两小时前AOF压根没开。那一刻我想明白一件事——聊Redis持久化光知道有RDB和AOF这俩词远远不够你得清楚自己的配置在什么故障下能扛住、什么情况下会裸奔。这篇文章打算把RDB和AOF从头到尾拆开讲底层怎么工作、恢复速度差在哪、丢失窗口由什么决定再给一套生产环境可以直接落地的配置和备份恢复方案。最后是我这些年踩过的几个坑每一个都有完整的排查链路照着走基本能少熬几个夜。1. 先想清楚Redis默认的“不持久”才是大多数数据丢失的根源1.1 内存数据库的宿命断电就失忆Redis的所有数据默认都活在内存里。内存的特点是快但怕断电、怕进程被杀。所谓持久化就是给这份“短期记忆”找个外置备份要么定期把整个内存状态拍成一张快照存到磁盘也就是RDB要么把每一条写命令都记成流水账也就是AOF。这里有个很容易被忽略的点很多人以为Redis开箱就是持久化的其实默认配置只是“有条件地定时拍快照”。默认的save规则通常是 save 900 1 / save 300 10 / save 60 10000 这类意思是“900秒内至少有1次写就存一次快照”“300秒内至少有10次写就存一次”。听起来好像一直在存实际上两次快照之间的窗口里所有写入都可能丢。1.2 默认配置下的丢失窗口到底有多大用最常见的默认快照规则举例如果你的业务每半小时才产生一次写操作那么save 900 1这条规则意味着距离上次快照超过900秒之后只要发生了1次写就会触发一次BGSAVE。看起来最多丢15分钟但注意如果这900秒内有写入却没有立即触发要等到下一个检查周期满足条件才执行实际窗口可能接近规则时间的两倍。这个窗口就是RPORecovery Point Objective的含义你能容忍数据回到哪个时间点。工程上聊“数据不丢失”不是说一条都不能丢而是把丢失窗口压缩到业务可接受的范围。比如AOF的everysec策略RPO大概是一秒而单独依赖RDBRPO可能是几分钟到几小时完全看你的快照频率。1.3 除了宕机还得防人为误操作丢数据的原因不只是机器宕机。我见过太多案例是有人在生产环境执行了FLUSHALL或者批量DEL写错了key前缀瞬间清掉一片数据。持久化机制能不能拦住这种事故AOF在这种场景下能当“后悔药”RDB只能恢复到最近一次快照误操作之后的新数据基本找不回来。所以完整的方案一定要把实时持久化、定期备份、命令审计三者结合起来缺一不可。2. RDB快照fork子进程搞定全量备份但窗口期由save规则决定2.1 RDB文件到底是什么RDB是Redis在某个时间点把全部数据序列化后写出的二进制文件默认叫dump.rdb放在dir配置指定的目录里。它不是命令日志更像是一张“内存全家福”记录了那一刻所有key和value的最终状态。因为不涉及逐条重放加载时直接把整张快照读进内存恢复速度是所有持久化方式里最快的。手动触发有两种方式SAVE是同步执行主进程直接阻塞直到写完生产环境基本不用BGSAVE是fork一个子进程去写主进程继续服务这是日常最常用的。除了手动执行主从复制全量同步、SHUTDOWN正常关闭等场景也会触发RDB生成。2.2 save规则怎么理解才不踩坑配置文件里可以写多条save规则任意一条满足就会触发BGSAVE# 900秒内至少有1次写操作 save 900 1 # 300秒内至少有10次写操作 save 300 10 # 60秒内至少有10000次写操作 save 60 10000想关闭自动快照写成save 就行。很多人看到“60秒10000次”觉得条件很高实际在写入量大的业务里这个规则反而会频繁触发BGSAVE后面我会说到它和fork耗时叠加带来的毛刺问题。2.3 BGSAVE背后的COW机制为什么快照不阻塞却也不是完全免费BGSAVE能边服务边备份靠的是Linux的fork和写时复制Copy-On-Write。fork的一瞬间子进程和父进程共享同一份物理内存页只是各自有一份页表。子进程开始遍历数据库并写RDB文件时它看到的是fork那一刻的内存状态如果父进程在这期间执行了写操作内核会把被修改的内存页复制一份给父进程子进程手里的页保持原样不动。这就像拍照时先把底片定格之后的修改不会再污染底片但每修改一页都得额外准备一张“新胶片”。所以BGSAVE期间的代价有两块一是fork本身要复制页表实例内存越大、页表项越多fork耗时越长期间主进程会出现一次短暂的阻塞二是快照过程中写入越密集内存额外消耗和磁盘IO压力就越大。2.4 RDB的先天缺陷最后一张全家福之后的事都没记下来RDB最明显的问题是丢失窗口由快照频率决定。假设你每30分钟快照一次14:30完成了最后一次BGSAVE14:50实例突然宕机那么14:30到14:50之间所有写入全部丢失。平均来看RPO是快照间隔的一半最坏情况是接近整个间隔。有同学会想那我让save规则更激进一点行不行比如每10秒存一次。理论上可以但每次BGSAVE都有fork成本高频快照在写入量大的实例上会让性能变得很难看。正确的做法不是靠RDB解决低丢失窗口而是引入AOF让RDB回归它最擅长的领域冷备、归档、快速恢复兜底。2.5 RDB的适用场景和检查项我自己用RDB主要做三件事异地冷备、版本归档、大实例重启时的快速拉起。运维上建议盯住几个指标info persistence里的rdb_last_bgsave_status正常应该是ok一旦变成err说明最近一次快照失败rdb_last_bgsave_time_sec如果耗时异常增长说明磁盘或内存状态有问题info stats里的latest_fork_usec用来评估fork对主进程的影响。3. AOF Appendonly把每一条写命令记进流水账丢了也能重放找回3.1 AOF的写入链路从内存到磁盘有三道门AOF的工作方式是把每次写命令先追加到内存里的aof_buf再按策略刷到系统内核缓冲区最后调用fsync让数据真正落到磁盘。这里的关键是“刷到内核缓冲区”和“fsync落盘”是两回事操作系统为了性能通常会攒一批数据再写盘如果进程崩溃或断电内核缓冲区里没落盘的数据就丢了。appendfsync参数控制的就是fsync的时机。3.2 appendfsync三档对比security和性能的取舍配置行为丢失窗口性能影响适用场景always每条写命令都fsync理论上极低几乎只丢正在执行瞬间的命令最差吞吐下降明显写量不大但数据极其重要everysec每秒fsync一次最多丢1秒左右较好生产最常用大多数业务默认选择no由操作系统决定刷盘时机可能丢几秒甚至更久最好纯缓存、可接受丢失需要澄清一个误区always也不是绝对零丢失。命令刚到达Redis还没被处理完时进程被强杀或者执行了fsync但磁盘硬件层面出了故障这些极端情况任何单机持久化都挡不住。所以“数据不丢失”的正确理解是把丢失概率和窗口压缩到极低而不是物理上100%保证。3.3 AOF文件长什么样可读的命令流水账AOF文件记录的是RESP协议格式的文本命令为了直观我把控制符去掉大致长这样*3 $3 SET $5 mykey $7 myvalue *2 $6 SELECT $1 0每条命令都是一段结构化的文本因此你可以用编辑器或者脚本去定位、删除某一条命令。这个特性在误执行FLUSHALL的救援场景里简直是救命稻草后面我会详写。3.4 AOF膨胀与重写机制不是清空日志而是用当前状态重建账本AOF记录的是操作历史同一个key被反复SET一百次文件里就有一百条命令。时间一长AOF体积会远超实际数据量恢复时重放也会越来越慢。Redis的解决方案是AOF重写fork一个子进程按照当前数据集生成一组最精简的写命令把这些命令写入新的AOF文件同时主进程把重写期间新产生的命令记录到一个缓冲里等子进程写完后再追加到新文件尾部最后原子替换旧文件。所以重写不是简单删掉一半日志而是“用当前状态重建全新的流水账再把重建期间的增量补上去”。触发条件由两个配置控制auto-aof-rewrite-percentage 100AOF体积比上次重写后的体积增长超过100%时触发auto-aof-rewrite-min-size 64mbAOF小于64MB时不触发重写。也可以手动执行BGREWRITEAOF。3.5 AOF恢复和截断容错启动时该注意什么Redis启动时如果appendonly是yes并且存在AOF文件会优先加载AOF逐条重放命令否则才去找RDB。这里有个常见的坑开了AOF但AOF文件是空的Redis会认为“没有可恢复的历史”不会自动倒回去加载RDB。恢复数据时先想清楚自己是打算走RDB还是AOF再看加载顺序。AOF文件在宕机时可能末尾被截断。aof-load-truncated yes默认会让Redis忽略尾部坏数据继续启动并打印告警日志如果设为noRedis会拒绝启动需要你用redis-check-aof --fix修复。修复之前一定先备份原文件这个工具会把截断或损坏的部分拆出来另存但没法保证修复后数据完整只能尽量止损。4. RDB还是AOF一张表做决策以及“不丢失”的真正含义4.1 全面对比没有谁碾压谁只有场景适不适合对比项RDBAOF文件内容某个时间点的二进制快照每次写命令的文本日志恢复方式直接加载快照逐条重放命令恢复速度快慢AOF越大越慢文件体积紧凑通常更大靠重写控制丢失窗口取决于快照间隔取决于appendfsync策略可读性二进制不可读文本可读可修对主进程影响fork阻塞 COW内存开销写盘IO开销 重写时的fork开销擅长场景冷备、快速重启、归档低丢失窗口、误操作救援4.2 “数据不丢失”是个工程命题不是绝对命题和业务方对齐“不丢失”的标准最有效的办法就是谈RPO和RTO。RPO是你能找回数据到哪个时间点RTO是多久恢复服务。结合常见的业务容忍度我的建议大致是这样纯缓存场景数据丢了可以从数据库重建不持久化或只开RDB兜底登录态、订单状态、计数、排行榜AOF everysec 混合持久化 每日RDB备份交易、余额等高一致性场景AOF always 多副本同步 定期校验 冷备到异地超大内存实例恢复速度优先必须用混合持久化避免纯AOF重放几十分钟。4.3 主从复制不等于持久化有个认知我每年都要在团队里强调主从复制是内存到内存的同步不是落盘机制。主节点宕机后从节点如果没有开启持久化切换上来一样可能丢数据主节点把数据同步给从节点的过程中如果从节点自己也崩了那还是一样裸奔。所以从节点同样要按主节点的标准配置AOF和RDB或者至少定期从从节点上执行BGSAVE提取备份这样能避免高峰期在主力实例上fork。5. 混合持久化与生产落地方案在RDB速度和AOF安全之间做综合5.1 混合持久化一条AOF文件里既有快照又有增量Redis 4.0之后AOF重写时可以先用RDB格式在文件头部写一段全量快照再在快照之后追加增量命令。这就是aof-use-rdb-preamble配置项的作用。恢复的时候Redis先快速加载RDB段再重放少量增量命令既保住了RDB的加载速度又把丢失窗口压缩到AOF的秒级水平。生产环境我建议显式开启不要依赖默认值。5.2 一份可以直接抄的生产配置# AOF开关与刷盘策略 appendonly yes appendfilename appendonly.aof appendfsync everysec no-appendfsync-on-rewrite no # AOF重写触发条件 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # 混合持久化 aof-use-rdb-preamble yes # RDB快照规则 save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes # 数据目录 dir /data/redis这里重点说两个配置的考虑。no-appendfsync-on-rewrite no意味着AOF重写期间依然坚持everysec的fsync策略虽然磁盘压力会大一点但不会在重写过程中把丢失窗口拉宽如果你非常在意重写时的磁盘IO可以临时设为yes但必须接受窗口变大的代价。stop-writes-on-bgsave-error yes则是一道保险当磁盘故障导致BGSAVE失败时Redis会拒绝后续写入宁可业务报错也不让数据在无人知情的情况下继续扩大丢失面。5.3 定时备份脚本导出、校验、保留一个都不能少RDB和AOF解决了实例级别的崩溃恢复但解决不了“整机磁盘损坏”“文件被误删”“勒索病毒”这类问题所以定期把备份文件复制到异机或对象存储是必须的。下面这个脚本的思路是在凌晨低谷触发一次BGSAVE等它真正完成后再拷贝RDB#!/usr/bin/env bash HOST127.0.0.1 PORT6379 PASSyour-password BACKUP_DIR/backup/redis DATE$(date %F_%H%M%S) redis-cli -h $HOST -p $PORT -a $PASS BGSAVE /dev/null # 等待 BGSAVE 完成轮询 rdb_bgsave_in_progress while true; do state$(redis-cli -h $HOST -p $PORT -a $PASS INFO persistence | grep rdb_bgsave_in_progress) if echo $state | grep -q :0; then break fi sleep 1 done cp /data/redis/dump.rdb $BACKUP_DIR/redis-$DATE.rdb # 只保留最近14份RDB避免磁盘被备份撑爆 ls -1t $BACKUP_DIR/redis-*.rdb | tail -n 15 | xargs -r rm -f这个脚本里有几个实战细节。第一一定要等rdb_bgsave_in_progress归零再拷贝否则拷到的可能是写了一半的RDB文件。第二想要连AOF一起备份的话需要等aof_rewrite_in_progress也为0不然会拷到重写中的中间态。第三大实例上BGSAVE对主节点有fork影响所以备份任务必须错开业务高峰凌晨执行是最常见的安排如果业务量实在太大更稳的做法是从从节点上提取RDB。5.4 恢复演练平时没跑通真出故障就是事故备份文件如果不做恢复验证等于没有备份。我建议至少每季度做一次完整的恢复演练准备一台干净、版本一致的临时实例把RDB和AOF拷贝到它的dir目录先以appendonly no临时启动看看加载RDB后keyspace数量是否符合预期再按正常配置启动确认AOF路径的恢复耗时和最终key数量记录恢复耗时用来核对RTO是否达标。我第一次做演练就发现了一个隐患备份脚本的执行时间和AOF自动重写撞在一起导致备份出来的文件短期处于不一致状态。这种问题只有真的把备份文件拉到新实例上加载一遍才能暴露纸上谈兵是发现不了的。6. 生产环境踩过的坑从fork阻塞到误删恢复的完整排查链路6.1 坑一默认配置直接上线宕机后丢两个小时数据有次帮一个团队排查签到活动的问题。他们Redis只用了默认配置以为“开了持久化”结果晚上机器被OOM killer杀掉重启后最近两个小时的签到记录全部消失。上去查info persistencerdb_last_save_time显示上一次快照是两个小时前AOF压根没开。虽然他们活动期间的写入量不小但默认save规则里“300秒10次”这类条件未必持续满足快照被拖到了很晚才触发窗口自然就大了。这个案例的解决方式很简单开AOF everysec把快照规则改到业务低谷。但真正值得记住的是教训——任何承载业务数据的Redis上线检查清单的第一项就应该是持久化配置审计而不是功能测试全过就觉得万事大吉。6.2 坑二内存20GB后每次BGSAVE都是一次延迟毛刺另一个案例是实例高峰期p99延迟从5ms飙到300ms每天固定在几个时间点出现。当时先排除了慢查询然后看info stats里的latest_fork_usec发现毛刺时间和fork耗时高度重合。再一查配置自动save规则正好把BGSAVE触发放到了流量高峰期。实例内存已经到了20GB级别fork一次要复制海量页表直接带来数百毫秒到秒级的主进程停顿。解决思路是三个方向同时动把自动快照关掉由外部定时任务在凌晨低谷执行BGSAVE把实例里的大key拆分降低单实例内存压力备份提取改到从节点上去做。同时开了latency-monitor-threshold让类似的问题下次能提前暴露在延迟监控里。记住一个原则RDB不是“后台写个文件”那么简单fork带来的延迟毛刺是真实存在的配置自动快照前一定要算好业务高峰。6.3 坑三AOF重写和备份脚本撞车凌晨磁盘报警有一次凌晨磁盘报警AOF文件疯长到几十GB日志里BGREWRITEAOF反复失败。排查后发现几个问题叠在一起auto-aof-rewrite-min-size配得太小导致重写过于频繁定时备份脚本在这个时间点也在执行BGSAVE和拷贝文件磁盘IO叠加到了极限重写失败后会不停重试进一步加剧磁盘压力。这类问题把配置调优就能解决适当调大min-size错开备份和重写的执行窗口监控aof_rewrite_in_progress和aof_last_bgrewrite_status重写失败时及时告警。AOF重写虽然是自动化的但你得给它留资源、设阈值而不是丢给Redis自己去撞运气。6.4 坑四误执行FLUSHALL后靠AOF“后悔药”救回一命有位同事调试脚本时连到了生产库一条FLUSHALL下去业务侧数据瞬间全清。这时候最重要的是两件事第一时间停止所有写入第一时间把当前AOF文件完整复制一份出来。为什么这么急因为一旦AOF重写自动触发重写出来的新文件是基于“当前已清空的数据集”生成的旧AOF被替换掉后悔药就没了。止损完成后把AOF里FLUSHALL或FLUSHDB对应的整条命令块删掉再用正常配置启动Redis数据就可以恢复到误操作前的状态。如果用的是everysec最多丢一秒左右的写入如果开了always基本能恢复到最近一条命令。复盘时我只留了一句话生产环境把危险命令rename掉操作前必须确认连接串命令审计日志不能省。6.5 坑五开了AOF后恢复仍是空的问题出在加载顺序有个同事说他把RDB放回实例了重启后Key全没了。过去一看appendonly yesAOF文件存在但内容是空的。Redis的加载逻辑是优先AOF再RDB空的AOF意味着“没有可重放的历史”它也不会回头去找RDB。这时候数据当然看起来是空的。正确的处置路径是先备份现有的AOF临时关闭appendonly重启让Redis加载RDB确认数据完整后再重新开启AOF并执行BGREWRITEAOF让新的AOF基于当前数据集重建。这个案例提醒我恢复数据时第一件事不是急着放文件而是先想清楚这次恢复到底是走RDB路径还是AOF路径。6.6 补充提醒新版Redis的AOF结构变了如果你用的是较新版本的RedisAOF不再只是单个appendonly.aof文件而是引入了一套manifest机制重写和恢复的行为跟老版本不完全一样。跨版本升级后一定要重新走一遍完整的备份和恢复演练别拿老经验直接套新版本也别拿新版本的备份文件丢到老版本上去加载。版本兼容问题碰上一次就是大事故。最后再说两句我自己的习惯是每接手一套Redis第一件事就是跑一遍INFO persistence把rdb_last_bgsave_status、aof_rewrite_in_progress、aof_last_bgrewrite_status这些指标接进监控。数据不丢失从来不是靠某一个参数而是“持久化配置备份策略恢复演练”三者一起兜底。送大家一个实操习惯任何一次手动执行FLUSHALL或批量删除之前先触发一次BGSAVE把dump.rdb复制出来留个底。多花三十秒可能省掉一次通宵。