
很多团队把 Redis 当一个大号的 HashMap 用平时只往里塞数据从没想过它也会“失忆”。我印象特别深的一次事故线上活动刚结束运营正在跑对账脚本突然 Redis 进程挂掉等拉起来热榜、库存缓存、用户会话几乎全空了——因为当初部署时图省事持久化根本没开。那一刻所有人都在问数据还能找回来吗答案是不能。那次之后我把 Redis 持久化整套机制从 RDB 到 AOF、再到混合持久化完整梳理了一遍。这篇文章就是把当时的笔记整理出来的重点讲清楚两件事一是 Redis 到底靠什么手段把内存数据落到磁盘二是生产环境到底该选哪种组合以及选完之后怎么验证它真的能救你一次。适合正在研究“Redis 重启后如何保证数据还在”的同学尤其是刚接手 Redis 生产环境的开发或运维看完能少踩好几个坑。1. 持久化的本质那只看不见的内存大象Redis 作为内存数据库最核心的优势是快但代价就是数据一直在内存里进程重启、机器断电、OOM 被杀掉都会把这些数据直接清零。很多人以为 Redis 从诞生起就自带持久化其实不是。早期 Redis 定位就是纯缓存服务器数据丢了就丢了再从数据库里回源重建就好。后来大家发现Redis 不仅能做缓存还能存计数、排行榜、会话、订单状态这类相对关键的数据官方才开始引入持久化机制。所以第一个需要建立的认知是持久化不是 Redis 的“默认天赋”而是后来演进出来的能力。它本质上是一套“让内存里的数据在磁盘上有备份”的机制相当于我们给内存这头大象拍了一套存档。目前 Redis 官方提供三种持久化手段RDB 快照、AOF 追加日志、以及两者结合的混合持久化。我用一个生活化的类比帮你看清全貌RDB 就像定期给系统拍一张全身照照片里是“那个时间点”瞬间的完整数据AOF 则像一直坐在旁边记流水账的账房先生每个写操作都被记录将来要还原数据就按账本重放一遍混合模式更像“先拍快照再接着记流水账”重启时直接读照片再用账本里最后的一小段记录补全。理解了这三种手段再看配置文件和监控指标就不会迷茫。但在此之前必须先破除一个常见误解持久化不保证“绝对不丢数据”。它只是把你的丢失窗口控制在一个可接受范围内。真正的数据不丢需要持久化、主从复制、定期备份三者一起上缺一个都不稳。下面分别拆解。2. RDB 快照机制拍照存档的代价与边界2.1 RDB 是二进制序列化的全量快照RDBRedis DataBase是 Redis 默认开启的持久化方式核心思路很简单把某个时刻内存中的数据集序列化成一个紧凑的二进制文件默认叫 dump.rdb。这个文件包含整棵数据结构包括字符串、哈希、列表、集合、有序集合等。你可以在命令行手动触发快照redis-cli save redis-cli bgsavesave是同步阻塞的会让 Redis 主进程停在那里直到快照写完生产环境基本不碰。bgsave是官方推荐的方式因为它在后台执行主进程还能继续服务。2.2 fork 与写时复制bgsave 不卡主线程的秘密bgsave背后最核心的机制是 Linux 的 fork。主进程调用 fork 后会生成一个与当前进程几乎完全一样的子进程这个子进程负责把内存数据写入临时 RDB 文件。关键在于fork 出来的子进程不复制全部内存而是与父进程共享同一份物理内存只有在某一方要修改某块内存页时系统才会真正复制那一页这就是写时复制Copy-On-WriteCOW。对于 RDB 来说子进程只读数据不修改因此父进程每秒写入的新数据量越大因 COW 产生的内存复制也越多。实测中大实例执行 bgsave 时 Resident Set Size 通常会上涨 20% 到 50%如果实例本身已经接近物理内存上限这个涨幅可能直接引发 OOM。另外fork 调用本身是一次同步操作内存越大fork 耗时越长。一个 10GB 左右的实例fork 可能从几毫秒飙到几百毫秒极端情况下甚至达到秒级。Redis 6.0 之后可以通过redis-cli latency doctor或日志观察 fork 事件耗时生产监控里建议记录latest_fork_usec这个指标。2.3 自动触发与丢数据窗口Redis 配置文件里默认有三条自动触发规则save 900 1 save 300 10 save 60 10000含义是900 秒内有至少 1 个键被修改则触发 bgsave300 秒内有至少 10 个键被修改则触发60 秒内有至少 10000 个键被修改则触发。这套规则看似合理但在生产环境很容易造成 IO 突刺特别是高峰期写流量很大时60 秒 10000 次修改轻松触碰后台快照频繁启动磁盘写入压力骤增。更重要的是RDB 的丢数据窗口等于“两次快照之间的间隔”。如果上一次快照是 10 分钟前这 10 分钟里写入了 500 万条数据而进程突然崩溃那这 500 万条数据全部没了。不是你丢了 1 秒而是丢了整整一个快照周期。2.4 RDB 恢复过程与优缺点盘点Redis 启动时如果配置了dbfilename并且dir目录下存在对应文件它会尝试加载 RDB 到内存。加载过程是直接重建内存数据不像 AOF 那样一条条重放命令所以速度非常快。对一个 10GB 的 RDB 文件通常几秒到十几秒就能加载完。列一下 RDB 的优缺点优势劣势文件紧凑便于备份和传输丢失两次快照之间的数据加载恢复速度极快bgsave 时 fork 可能造成主进程卡顿适合周期性备份、冷备归档大实例 fork 时有内存放大风险主从复制全量同步也依赖 RDB自动触发配置不当会频繁写盘这里有个很容易被忽略的点主从复制里的全量同步本质上就是主节点生成一份 RDB 传给从节点。所以你有没有开启 RDB主从复制都会用到快照文件。也就是说只要搭了主从架构RDB 相关机制就已经在为你服务了。3. AOF 重放机制写一条记一条但账本会越来越厚3.1 AOF 是写后日志AOFAppend Only File的逻辑与 RDB 完全不同。它会记录每条会导致数据变化的写命令然后在重启时把这些命令重新执行一遍从而还原数据。Redis 的 AOF 是“写后记录”命令先执行执行成功后再把命令追加到 AOF 文件尾部。这和数据库常见的写前日志思路完全相反。为什么 Redis 这么选一个直接原因是性能优先。如果先写日志再执行命令每条写操作都要先等待日志落盘延迟暴涨而写后记录则让 Redis 主线程先服务请求日志生成过程异步化。缺点也明显如果一个写命令已经执行成功但紧接着进程崩溃AOF 恰好没记上数据就丢了。所以 AOF 不是真正的“零丢失”只是把丢失窗口缩得很小。3.2 appendfsync 三种策略对比AOF 能不能保住数据关键在于刷盘频率也就是appendfsync配置项。先看配置appendfsync always appendfsync everysec appendfsync no三种策略的差别用一个表说清楚策略刷盘时机丢数据窗口吞吐量影响always每条写命令执行后立即 fsync极小但断电瞬间也可能受影响极大实测普通 SSD 上高并发时 QPS 会断崖式下跌everysec每秒批量 fsync 一次最多丢约 1 秒数据很小几乎不影响主流程no交给操作系统决定何时刷盘可能丢失最后一段时间的全部增量最小生产环境我基本只用everysec。它把选择性决策做得很明智每秒一次集群刷盘保证性能的同时把丢失窗口压到 1 秒左右。always听着最安全但代价是主线程每写一次都要等着硬件落盘相当于把 Redis 变成了一个脆弱的同步写入器吞吐量常常从十万级降到几千级还会放大磁盘延时波动。还要注意一种特殊情况即便配置了everysec如果恰好下一秒的批量写入发生前进程崩溃这 1 秒内的写入会丢极端断电场景下操作系统页缓存也未必完全落盘可能丢得比 1 秒更多。所以严格讲AOF 也是“尽力”保证数据不丢而不是数学意义上绝对不丢。3.3 AOF 重写账本太厚了得重新誊一遍AOF 文件随着写命令不断追加会无限膨胀。比如你往一个 list 里 push 了 10 万条数据AOF 就记 10 万条命令将来重放时这 10 万条命令一条条执行速度慢文件也占磁盘。这时就需要 AOF 重写bgrewriteaof。AOF 重写不是“把旧文件压缩”而是“基于当前内存中的数据生成一组最能表达当前状态的最简命令”然后生成一个新的 AOF 文件。旧 AOF 文件里那些“加了又删、改了又改”的冗余操作全部被丢掉。触发重写有手动和自动两种redis-cli bgrewriteaof自动触发条件由这两个参数控制auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb含义是当 AOF 文件大小超过上次重写后文件大小的一倍100%且当前文件至少 64MB 时后台自动执行重写。注意重写同样要 fork同样有内存放大、fork 停顿的问题。而且重写期间主进程还在接收新写入新增命令会同时被缓冲到内存等新文件写完后追加进去。如果缓冲区太大瞬时内存压力不容小觑。3.4 AOF 文件加载与损坏修复AOF 保存的是 RESP 协议格式的命令文本可读性比 RDB 好很多。Redis 启动时若开启 AOF会优先从 AOF 文件加载数据如果 AOF 文件末尾出现半截命令通常是因为进程在写文件时崩溃、磁盘写满或断电这时候 Redis 默认会拒绝启动并在日志里报错。千万别手忙脚乱直接删文件。先备份一份现场然后使用官方工具修复redis-check-aof --fix appendonly.aof工具会将文件里不完整的尾部截断掉让 Redis 能重新加载。代价是丢失尾部那部分尚未完整写入的数据。顺带提醒修复前一定要先备份原始文件因为修复过程会把残缺尾段直接删掉一旦误操作连恢复的可能性都没了。3.5 一次 AOF 截断事故的完整复盘我有一次在测试环境模拟磁盘写满时遇到过典型的 AOF 加载失败。当时排查链路是这样的查看日志看到Bad file format reading the append only file检查磁盘发现/data分区使用率达到 100%AOF 文件大小已经逼近磁盘上限确认是磁盘满导致写入过程中断文件尾部的命令只有一半先cp appendonly.aof appendonly.aof.bak备份然后执行redis-check-aof --fix appendonly.aof再启动 Redis加载成功清理磁盘空间并立刻手动触发一次 bgsave 做全量备份。这个排查链路不算复杂但很多人在第 4 步就漏掉了。一旦修复程序把残缺尾部截掉你连“尝试恢复那半条命令”的机会都没了。保持现场是最重要的第一反应。4. 混合持久化既想要快恢复又想要少丢数据4.1 单一方案的两难RDB 恢复快但丢数据窗口大AOF 丢数据少但重放慢。当数据量达到几十 GB 甚至上百 GB 时这个矛盾会非常尖锐。我见过一个团队关闭 RDB 只开 AOF 的情况每次重启都要等十几分钟期间 Redis 对外不可用业务方急得跳脚。有没有办法同时保留两者的优点4.2 aof-use-rdb-preamble 的设计思路Redis 4.0 开始提供混合持久化配置项是aof-use-rdb-preamble yes开启后AOF 重写策略彻底改变重写生成的 AOF 文件不再是一堆纯命令而是先写入一个 RDB 格式的全量数据快照头然后把重写期间新产生的写命令以 AOF 格式追加到文件尾部。这样的 AOF 文件结构就是“RDB 头 AOF 尾”。Redis 启动加载时会先识别文件头部的 RDB 魔数用 RDB 加载器快速载入全量数据再继续按 AOF 模式执行尾部命令完成追加增量重放。这个设计同时解决两个问题恢复速度接近纯 RDB因为大部分数据都是批量载入不需要逐条执行数据丢失窗口由appendfsync决定开启 everysec 后同样最多丢 1 秒数据。所以混合持久化不是“RDB 和 AOF 两个都开”的简单叠加而是一种重新设计过的文件格式。在 Redis 4.0 的所有生产环境里我都建议直接开启。4.3 实际能快多少用一个不算极端的例子算笔账。假如纯 AOF 文件有 20GB命令条数上千万重放时需要逐条解析命令、查找 key、构造数据结构慢的时候每秒钟只能处理几十万条全部重放可能要几分钟。混合模式下AOF 文件里大部分空间是 RDB 头恢复时相当于加载一份十几 GB 的 RDB 快照耗时通常在十秒级只有重写期间积累的那一小段增量命令需要逐条执行量级通常只有几百 MB 甚至更小恢复总耗时能压到几十秒以内。4.4 注意兼容性边界混合持久化也有一个边界AOF 文件无法被老版本 Redis 识别。如果你要跨大版本降级或者想用老版本工具做临时恢复会碰到问题。好在生产环境基本都会持续升级 Redis 版本这个风险不算常见。但做灾备恢复方案时最好在文档里明确标注备份文件对应的 Redis 大版本避免关键时候版本不匹配。5. 实测对比与场景选型RDB、AOF 还是混合5.1 一张表看清三种方案我把三种持久化方案的关键维度放在一起对比方便你直接判断对比维度RDBAOFeverysec混合持久化恢复速度很快直接重建内存慢逐条重放命令快RDB 头批量载入 少量命令重放数据丢失窗口取决于快照间隔可能丢很多最多约 1 秒最多约 1 秒文件体积紧凑很大命令冗余多比纯 AOF 小很多写性能影响bgsave 时 fork 停顿其他时间无感everysec 主流程几乎无感重写时同样有 fork 停顿日常无感磁盘 IO周期性突发写盘持续低量写盘周期性突发 持续低量内存放大bgsave 期间 RSS 可能上涨 20%-50%重写期间同样有放大重写期间同样有放大这张表看完你大概能明白为什么我不建议在数据关键场景单独用 RDB也不建议用纯 AOF 处理超大实例。5.2 按场景给出选型建议第一类纯缓存场景丢了也无所谓数据全部能从其他源回填。这种情况完全可以关闭持久化省下磁盘 IO 留给业务。但要和业务方达成明确共识别等到崩了才来扯“为什么缓存会变成单一数据源”。第二类Redis 里存的是核心业务数据比如订单状态、库存计数、用户资产。这种场景直接选混合持久化appendfsync everysec同时做主从复制从库不承担写流量专门负责兜底。第三类追求最快启动和主从切换速度能接受分钟级数据丢失。比如一些流量分析、实时计算中间态可以选 RDB 加主从甚至把自动快照关闭由外部调度系统在低峰期触发 bgsave。第四类数据强一致要求一点都不能丢。坦白说单靠 Redis 持久化无法做到 100% 不丢appendfsync always也只是尽量这种需求更适合交给真正的事务型数据库或借助外部同步方案处理。Redis 永远不是“绝对不丢”的最佳答案。5.3 一份可以直接落地的配置模板这里给出我在生产环境常用的一套核心配置基于 Redis 6.x/7.xappendonly yes appendfilename appendonly.aof appendfsync everysec no-appendfsync-on-rewrite no aof-use-rdb-preamble yes save dir /data/redis dbfilename dump.rdb几个配置说明appendonly yes开启 AOF这是数据不丢的主力appendfsync everysec在性能和安全性之间取平衡aof-use-rdb-preamble yes开启混合持久化save 关闭默认 RDB 自动快照避免高峰期后台快照与主业务抢磁盘 IOno-appendfsync-on-rewrite no保持默认即 AOF 重写期间仍然正常刷盘。如果设为 yes重写期间 AOF 不刷盘变相扩大丢数据窗口我一般不推荐。这套配置的石料是“AOF 保证不丢重写保证文件不无限膨胀RDB 作为离线备份兜底”。注意关闭自动 RDB 后一定要用 crontab 或运维平台定期执行bgsave否则连离线备份都没有。5.4 监控命令与健康检查持久化不是配完就一劳永逸日常需要看几个关键指标。最常用的命令是redis-cli info persistence关注这些字段rdb_last_bgsave_status最近一次 RDB 快照是否成功ok 为正常aof_last_write_status最近一次 AOF 写入是否成功aof_last_bgrewrite_status最近一次 AOF 重写是否成功aof_rewrite_in_progress重写是否正在进行rdb_bgsave_in_progress快照是否正在进行。实际运维中可以写一个简单的健康检查脚本定时拉last_bgsave_status和last_write_status一旦发现不是 ok 就告警。别等磁盘满了、文件坏了、崩溃了才发现持久化早就出问题了。6. 恢复演练与故障复盘持久化最后的临门一脚6.1 为什么一定要做恢复演练很多团队处理持久化问题时最大的盲区是只关心“有没有配置对”从不关心“能不能真的还原”。配置了一万年崩溃一次照样手忙脚乱。恢复演练的核心目标很简单在安全环境里模拟进程崩溃、磁盘写满、断电拔盘等场景验证你手上的 RDB 或 AOF 文件能否成功加载加载耗时多长数据缺失范围是否在预期内。模拟崩溃的手段不复杂在测试实例上直接kill -9 redis_pid然后启动 Redis观察日志tail -f /data/redis/redis.log留意输出里有没有类似DB loaded from disk: 12.3 seconds的日志记录你的真实恢复耗时再对比配置时预期的超时窗口。别只在开发环境做一遍就完事建议每个月都做一次特别是大版本升级后持久化文件格式可能变化旧演练结果完全失效。6.2 一套完整的故障处理流程把演练流程沉淀成操作清单后真出故障时照着走能减少 80% 的无谓紧张。第一步确认故障现象。进程是不是挂了日志里报的是什么错磁盘满、AOF 校验失败、RDB 加载失败三种场景的处理方式完全不同。第二步保护现场。先把 RDB、AOF 相关文件复制一份到独立目录再开始修复操作。修复工具会把损坏内容截断没有备份就只能硬着头皮接受。第三步区分是加载问题还是数据缺失问题。用redis-check-aof修复 AOF用redis-check-rdb校验 RDB先确认文件格式是否完好再确认数据时间范围是否满足要求。第四步选恢复路径。主库坏了用从库顶从库也没有的话再用备份文件通过redis-cli导入。优先考虑主从切换直接靠备份文件原地恢复是用户最后的路线。第五步服务重启后立刻手动触发一次bgsave生成新的干净备份点并检查监控、告警是否都恢复。6.3 一次因为磁盘告警被淹没导致的事故复盘那次事故让我印象很深的其实是根因磁盘空间早就超过 90%告警邮件被人忽略了。系统没有自动扩容也没有自动清理策略最终磁盘写满AOF 写入中断进程随后被 OOM 机制杀掉重启时 AOF 文件尾部不完整Redis 直接拒绝启动。复盘下来有三个教训第一磁盘水位告警必须和持久化状态联动磁盘告警被淹没等于没有告警 第二AOF 重写期间文件体积可能瞬间膨胀到原来的一倍以上磁盘预留空间要按“当前 AOF 大小 数据库全量体积 20% 余量”来估算 第三恢复演练不应该只练正常启动还应该专门演练一次“磁盘写满后 AOF 截断”的极端场景遇到时才知道redis-check-aof --fix的输出到底意味着什么。那次之后我把故障演练里加入了“模拟磁盘写满”这一项并且把持久化监控指标接入了值班告警系统没有人能再把磁盘风险“看漏”。6.4 异地备份才是最后的保险持久化文件如果只存在于一台机器上那它和不存在没什么区别。机器硬盘损坏、机房断电、误删目录都会让它瞬间消失。我个人的习惯是每天把 RDB 备份传一份到异地存储保留最近 7 天同时把 AOF 也按小时级增量同步到异地。实际落地不复杂一条 crontab 就能完成大部分工作0 2 * * * redis-cli bgsave sleep 30 cp /data/redis/dump.rdb /backup/dump-$(date \%F).rdb再把/backup目录同步到远程对象存储或另一台独立服务器即可。不要省略最后一步本地备份永远不能作为灾难恢复的完整答案。最后再说一个我自己的习惯每次改完持久化相关配置我都会顺手跑一次redis-cli config rewrite确保配置文件与运行期参数一致。这个动作成本很低但能避免很多“改完忘了保存重启后配置回滚”的低级事故。Redis 持久化不是选 A 还是选 B 的单选而是一套组合方案混合持久化保证恢复快、丢失少主从复制保证高可用定期备份和异地备份保证灾难恢复。真正到了出事那天文档里写的所有理论都不如你之前亲手演练过一遍来得可靠。希望这篇文章能帮你把 Redis 的数据安全底兜得更稳一些。