1. 先聊一个残酷的现实持久化没配好Redis就是一只会漏水的桶1.1 一次让我印象深刻的线上宕机事故有次半夜被电话叫醒说线上Redis主节点挂了业务方反馈用户购物车和登录状态丢了。我当时第一反应是重启恢复不就完了结果重启之后实例正常起来内存里的数据却少了一大截——往前查了查最近一个多小时写入的key全部消失。原因说出来其实很尴尬那台实例用的是默认RDB配置save规则是3600秒内至少有1次写、300秒内至少有100次写、60秒内至少有10000次才触发快照。而这是一个低峰期的缓存业务写量根本达不到60秒1万次的阈值于是最后一次有效快照还是一个小时前的。进程被系统OOM Killer干掉的那一刻内存里那一小时的新数据跟着一起没了。这个事故让我记住一件事Redis默认配置不等于安全配置。很多人以为Redis是数据库重启不会丢数据但实际上它首先是一个内存数据库持久化需要主动设计和验证。1.2 内存数据库为什么必须考虑持久化Redis所有数据默认都活在内存里读写速度快靠的就是不需要碰磁盘这个特性。但代价是进程异常退出、断电、操作系统重启、容器被调度到另一台机器内存里的东西说没就没。持久化解决的不仅是宕机不丢数据这一个问题它还承载着三层职责容灾恢复进程崩溃、机器故障后能把数据重新加载回来。数据迁移与备份从一台机器到另一台机器从本地到云主机把RDB文件拷贝过去就能恢复。审计与分析AOF文件里保留了写命令的文本记录某些场景下能回溯操作轨迹。Redis官方提供两种持久化方案RDBRedis Database内存快照和AOFAppend Only File追加写文件。两者思路完全不同一个像给整个数据集拍合影一个像给每条写操作记账。搞懂它们各自的原理、代价和适用边界比单纯背配置项重要得多。1.3 动手前需要准备的环境后面我会给出大量配置和命令建议你本地装一个Redis实例跟着操作。安装方式不多说官网下载源码编译、apt install redis-server、brew install redis、docker run -d --name redis-test redis:7.0都可以版本尽量选Redis 5.0以上最好直接上7.x因为新版AOF已经改成Multi Part结构排查思路和老版本有些差异。装好后用redis-cli info persistence看一眼当前状态你会看到rdb_bgsave_in_progress:0、aof_enabled:0之类的字段。后面所有实验都从这个输出开始。2. RDB一张按时间点拍摄的全量快照2.1 RDB的工作方式fork与写时复制RDB的思路用一句话概括在某个时间点把内存里的全量数据打成二进制包落盘成dump.rdb。触发方式分两种save和bgsave。save是同步阻塞式的执行期间Redis主进程停止处理所有命令数据量大的时候会造成明显卡顿生产环境基本没人直接用。真正用的是bgsave主进程调用fork()创建一个子进程由子进程负责把数据写入临时RDB文件写完后再原子替换旧文件主进程继续处理命令。这里最关键的是fork之后的内存共享机制。子进程刚被创建出来时和父进程共享同一份物理内存页并不需要把整个数据集复制一遍所以fork瞬间很快。但Redis是写入频繁的数据库父进程里每发生一次写操作操作系统就会把对应的内存页标记为脏页并复制一份给子进程这就是写时复制Copy On WriteCOW。你可以把父子进程想象成两个人在共看一本书本来都翻同一页。父进程要在页上做笔记系统会先复印一张干净的页面给他保证子进程看到的还是fork那一刻的内容。这个机制带来的副作用很直接bgsave期间写入越频繁被复制的脏页越多Redis实际内存占用就越高。之前我有个24GB的实例高峰期触发bgsave内存直接从24GB涨到接近30GB如果机器内存本来就紧张很容易触发swap甚至OOM。2.2 RDB的触发条件与配置项默认配置是Redis 7.0版本中自带的save 3600 1 save 300 100 save 60 10000 dbfilename dump.rdb dir ./ stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yessave后面的两个数字含义是时间间隔 修改次数只要两个条件同时满足任一组的阈值就触发一次bgsave。也就是说3600秒1小时内至少有1次写入300秒5分钟内至少有100次写入60秒内至少有1万次写入。用save 可以完全关闭RDB。stop-writes-on-bgsave-error yes意思是如果bgsave失败常见原因是磁盘满、权限不对Redis会拒绝写入命令这个策略虽然粗暴但能防止自以为有持久化、实际根本没备份的假象。除了自动触发还有几种场景会触发bgsave执行redis-cli bgsave手动触发shutdown关闭Redis时如果RDB已启用会尝试保存快照主从复制时从节点全量同步也可能触发主节点bgsave。实际运维中我很少依赖自动save规则因为低峰期写量达不到阈值快照间隔会被拉得很长数据丢失窗口不可控。更可靠的做法是写一个定时任务凌晨低峰期手动bgsave把RDB文件异地备份。2.3 dump.rdb文件长什么样RDB文件是二进制格式不是给人直接读的。文件内容包括固定开头REDIS五个字符加上版本号数据库编号与哈希表大小序列化后的key-value数据结尾的CRC64校验和用于加载时检查文件是否完整。rdbcompression yes表示对字符串类型的value使用LZF算法压缩所以实际文件体积通常比内存数据小。rdbchecksum yes会在写入和加载时计算CRC64文件损坏的话加载会失败Redis会拒绝启动。想快速确认文件是否正常可以用:redis-cli --rdb dump.rdb或者干脆把实例停掉重启看日志里有没有DB loaded from disk这类输出。2.4 RDB的省心之处和致命短板RDB最让人舒服的地方是运维简单优势说明文件紧凑压缩后的二进制文件体积小适合备份、传送到异地恢复极快加载RDB是直接反序列化灌入内存比重放命令快一个数量级迁移友好拷贝一份rdb文件到新机器即可完成数据迁移不影响主线程bgsave由子进程完成fork之外不阻塞查询短板同样明显数据丢失窗口大。两次快照之间所有写入都会丢默认配置下窗口可能以小时计。fork过程可能卡顿。实例内存越大、fork耗时越长我实测遇到过24GB实例在写入高峰期fork花了1.2秒期间所有命令排队。没有命令粒度。你无法得知快照之间到底执行过哪些写操作出了人为问题比如误删数据只能回滚到上一个快照点中间数据只能手动补。3. AOF每一笔写操作都被记账但那本账也有烦恼3.1 AOF写入链路与三档刷盘策略AOF的思路和RDB完全不同把每一条写命令以Redis序列化协议RESP的文本格式追加到文件末尾。你执行SET name tomAOF文件里就多一行对应的命令记录。重启时逐条重放这些命令就能还原数据。写入链路大概是这样的客户端发来写命令Redis在主进程中执行命令、修改内存数据同时把这条命令追加到内存中的aof_buf缓冲区按照刷盘策略决定什么时候把缓冲区内容真正交给操作系统写入磁盘操作系统把page cache中的数据持久化到磁盘。关键在第三步也就是appendfsync配置项它决定数据落盘的实时性appendfsync 值行为数据安全性能代价always每条写命令都调用fsync()强制落盘理论上零丢失最大写延迟明显升高everysec每秒调用一次fsync()批量刷盘最多丢1秒数据较小推荐no交给操作系统自行决定何时刷盘最危险可能丢数秒甚至更多数据最小很多人不理解fsync()到底做了什么。操作系统收到write调用后数据先进page cache真正的磁盘写入由内核决定。如果内核还没写盘就断电或者崩溃page cache里的内容就丢了。fsync()的作用是强制把脏页刷到磁盘确保数据不太监。everysec是大多数生产实例的默认选择它把数据丢失窗口控制在一秒以内同时每秒只调用一次fsync对性能影响很小。如果你觉得绝对不能让命令丢才需要开always但要做好写QPS下降的心理准备。no我基本不建议使用省下的性能远比不上数据风险。3.2 AOF重写为什么文件会越来越大AOF是追加写同样的key被反复修改一百次就会有一百条命令记录在里面比如SET counter 0 INCR counter INCR counter ...重复100次文件越来越大加载时越来越慢磁盘占用也越来越难看。解决方案是AOF重写根据当前内存里的数据生成一组能还原现状的最小命令集合。比如上面100条INCR重写后只保留一条SET counter 100。手动触发用redis-cli bgrewriteaof自动触发由配置控制auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb含义是当前AOF文件超过auto-aof-rewrite-min-size默认64MB且文件大小比上一次重写时增长超过100%时触发一次新的重写。注意AOF重写同样走fork子进程不会阻塞主线程但会和RDB的bgsave一样带来内存瞬间膨胀的问题。Redis 7.0之后AOF文件从单个appendonly.aof改成Multi Part结构一个基础文件base通常是某个时间点的RDB格式或AOF格式全量数据加若干个增长文件incr记录基础文件之后的新写入。这样做的好处是重写不再需要重写整个超大文件只需要生成新的base增量部分单独存放更稳。3.3 AOF文件格式与重启加载AOF文件是可读的文本文件比如执行SET username tom对应的AOF记录是*3 $3 SET $8 username $3 tom*3表示这条命令有三个参数$3表示接下来这个参数长度是3个字节以此类推。这种格式叫RESP协议Redis客户端和服务器之间通信用的也是它所以AOF其实就是在录制客户端发来的写命令。重启加载时Redis从头逐条解析并执行这些命令。数据量小的时候还好一旦AOF文件撑到几个GB重放过程可能要好几分钟。这个阶段Redis对外不可用除非配置了主从所以AOF恢复速度和RDB完全没法比。3.4 AOF的短板AOF的优点很吸引人——丢失窗口小、文件可读可修复但它有自己的代价性能开销即使开everysec高频写入场景下磁盘I/O压力也比RDB大开always则直接影响主线程写延迟。文件体积同一份数据集AOF通常比RDB大不少写到同样的命令RDB存的是最终结果的压缩快照。恢复慢逐条重放命令远慢于RDB的直接反序列化。配置复杂度追加写、重写、Multi Part概念比RDB多得多理解和排查成本都更高。4. 正面对比RDB和AOF在不同维度上的优劣格局4.1 一张核心对比表看穿本质对比维度RDBAOF数据安全丢失窗口大取决于save触发间隔always下零丢失everysec丢1秒恢复速度极快直接加载二进制快照慢逐条重放命令文件体积小LZF压缩大命令文本累积对主进程影响fork时有短暂阻塞写多时内存膨胀fsync频率决定I/O开销重写时同样fork运维友好度黑白分明配置简单概念多重写机制需要理解适用场景备份、迁移、缓存数据安全敏感的在线业务举一个我实测过的恢复耗时对比一个500万key、约6GB内存的实例RDB文件大概1.8GB加载耗时约45秒。同一实例开启AOF后文件膨胀到5GB多重启重放耗时接近4分钟。对这个体量的业务来说30秒和4分钟的差距是不可接受的。4.2 写放大与读放大的心理账用数据库领域的视角看RDB和AOF的差别本质上是写放大和读放大的权衡。RDB每触发一次就是全量数据落盘写放大极高哪怕只改了1个key也要把几GB快照重写一遍但读取时放大很低一次加载到位。AOF每次写入只有一条命令写放大低但恢复时需要重放大量历史命令读放大高。理解了这层关系你就会明白为什么不存在完美的持久化方案——RDB用频繁的写放大换来了快速恢复AOF用稀疏的写放大换来了数据安全。混合持久化就是试图在两者之间找到平衡点。4.3 一个常见的误解AOF不是绝对保险箱AOF看起来比RDB稳但千万别把开启AOF等同于数据绝对不丢。如果业务在高峰期刷盘策略是everysec那一秒内的数据照样丢AOF重写期间父进程收到的新命令会先记在重写缓冲区如果此时进程崩溃这些命令是否写进新文件取决于源码实现历史上出过bug人为误操作不在AOF的保护范围内——你执行了DEL keyAOF忠实地把这条命令写下来重启后照样执行删除。持久化只解决进程异常退出导致数据消失这一类问题不解决人把数据改坏了的问题。后者需要靠备份时间点恢复、命令审计和权限管控。5. 生产环境最容易踩的五个持久化坑含完整排查链路5.1 fork耗时长导致主线程卡顿症状监控显示Redis响应延迟周期性出现尖峰每次持续几百毫秒到几秒业务方反馈接口超时。排查链路执行redis-cli --latency -i 1观察延迟曲线的尖峰节奏打开redis-cli info stats重点看latest_fork_usec字段单位是微秒如果这个值超过500000即500毫秒说明fork阶段把主线程堵了很久再查info memory看used_memory_rss / used_memory的比值以及bgsave期间瞬时内存峰值用redis-cli --bigkeys扫描是否有超大key——大key会让fork后COW复制更多内存页加剧fork耗时。解决思路控制单实例内存实践经验是不要让单实例RSS超过10GB业务量大就拆分实例或集群避免在写入高峰期自动触发bgsave把save规则调低或改为定时手动执行如果已经严重到影响业务考虑切换AOF everysec并在低峰期重写。5.2 AOF文件尾部损坏导致的启动失败症状重启Redis时日志报错Bad file format reading the append only file实例启动失败。原因AOF文件尾部损坏常见于断电、docker容器被强杀、磁盘写入期间文件被截断。Redis加载AOF时会校验每条命令的RESP格式读到坏数据直接报错退出。排查与修复链路先备份损坏文件千万不要在原文件上直接操作cp appendonly.aof appendonly.aof.bak用官方自带的redis-check-aof检查修复redis-check-aof --fix appendonly.aof它会扫描文件并截断到最后一个合法命令位置确认后生成可用的文件启动Redis验证数据完整性对比dbsize、抽查关键key如果数据依然不对就用备份文件重新加载并考虑从从节点补数据。这个问题的关键教训是修复工具只会保证文件语法合法不会保证你的数据都在。它把损坏位置之后的所有内容都砍掉了中间可能包含大量有效命令。所以修复完必须做数据抽样比对。5.3 同时开启RDB和AOF时的优先级陷阱我见过有人同时开了两种持久化重启后发现数据不对却怎么都想不明白。原因很简单当AOF和RDB同时存在时Redis重启优先加载AOF文件而不是RDB。因为设计者默认AOF比RDB更新、数据更完整。这个陷阱最坑的地方在于如果你只是修改了RDB相关的配置比如改了dbfilename重启后发现数据没刷新还以为配置不生效。其实是因为AOF文件里存的数据覆盖了RDB文件的数据。生产实践经验如果只想要RDB来恢复先把AOF关掉重启一次让AOF文件失效如果只想要AOF那把save配置全部注释掉避免RDB持续生成干扰认知不要同时维护两份文件还指望它们保持一致除非你理解混合持久化模式。5.4 flushall误操作之后如何抢救这是运维事故里最容易让人手足无措的场景。Redis重启后并不会因为数据被清空而报错你只能靠持久化文件来恢复。假如你执行了flushall并且启用了AOF立刻找到当前实例使用的AOF文件复制一份出来用redis-check-aof检查是否为有效RESP命令流把AOF文件末尾的SELECT、FLUSHALL相关命令删掉可以用文本编辑工具但要小心文件结构再把文件放到一个临时Redis实例加载也可以直接把处理过的文件放回生产实例重启。如果只有RDB立即找到最近一份有效dump.rdb文件停止实例用这份文件替换当前目录文件后重启如果不知道哪份是有效的先备份现场再用rdb文件加载到一个隔离实例里验证内容。这类事故最有效的防线其实是事前配置把flushall、flushdb、key *这类高危命令通过rename-command重命名或者给应用方只分配无flushall权限的用户。我后来对所有生产实例一律执行了命令禁用再也不赌人性。5.5 关闭持久化的裸奔模式什么时候能忍很多团队因为业务是纯缓存直接把RDB和AOF全部关掉觉得丢了数据也无所谓。这本身没有问题但有几个前提值得确认上游数据库或消息队列里有原始数据缓存丢了可以重建实例加入主从架构即使主节点挂了从节点能顶上全量同步重建数据你对丢失所有缓存数据这件事有明确的业务预案而不是等到线上事故了才讨论。最危险的情况是自认为纯缓存无所谓结果后面有人把业务关键数据放进去或者主从架构没有正确配置主节点挂了连个替补的都没有。所以每次关掉持久化之前我都会问一句这个Redis如果今天数据全没了业务还能不能正常走完一天6. 我的选型建议混合持久化如何既要又要6.1 混合持久化RDB做骨架AOF做肉Redis 4.0之后提供了一种混合持久化方案配置项是aof-use-rdb-preamble yes。开启后AOF文件不再全是命令文本而是开头先写入一个RDB格式的全量快照后面的增量部分继续用AOF命令格式追加。加载时Redis优先读取AOF文件头部的RDB段直接把全量数据反序列化进内存然后接着重放后面的增量命令。这样恢复速度快接近纯RDB的水平不用逐条重放全量历史命令数据丢失窗口小增量部分仍然按appendfsync策略刷盘。这是我目前最推荐的生产配置。它不是银弹但确实把RDB和AOF的互补性发挥了出来。6.2 三类业务场景的推荐配置场景一纯缓存、数据可重建save appendonly no最多加一个低频RDB用于缓存预热。场景二一般业务缓存可容忍秒级丢失save 3600 1 appendonly yes appendfsync everysec aof-use-rdb-preamble yes auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb既有RDB做快速恢复又有AOF兜底秒级数据增长。场景三金融、订单、强一致场景save 3600 1 appendonly yes appendfsync always aof-use-rdb-preamble yesalways是官方唯一能做到逐命令落盘的策略代价是写性能明显下降必须通过压测确认该实例的写QPS天花板。6.3 监控、备份和恢复演练的日常操作配置再好没有日常巡检也是白搭。我日常做的几件事# 查看持久化状态 redis-cli info persistence # 手动RDB快照随后把rdb文件异地备份 redis-cli bgsave # 手动触发AOF重写控制文件体积 redis-cli bgrewriteaof备份策略上我习惯每天凌晨低峰期执行一次bgsave把dump.rdb传到独立存储保留最近7天AOF文件则由Redis自动重写机制控制不额外处理。每季度至少做一次全流程恢复演练拿一台空闲机器导入最新的持久化文件启动实例记录从备份文件就绪到数据完整可服务的时间。这个时间就是你真正遭遇故障时的期望恢复时长。另外强烈建议在压测环境就验证好持久化相关参数。我见过不少团队生产环境上线后才发现stop-writes-on-bgsave-error把写入全部拒绝了原因只是磁盘用满。压测阶段把磁盘写满、模拟断电、模拟进程kill能暴露很多文档里不会写的真实行为。以我个人的经验来说Redis持久化的价值不在于出事的时候能救命而在于你提前想好了各种故障场景下到底要付出多少数据代价。RDB和AOF不是竞争对手它们是同一枚硬币的两面一个让你恢复得快一个让你丢得少。生产上别二选一混合持久化加周期性备份再配上一条从节点和定时恢复演练这套组合拳打下来Redis才真正算得上是一个让人睡得着觉的存储组件。最后分享一个小建议不管选哪种方案都要把info persistence里各个字段的含意吃透至少做到看一眼就知道当前实例处于什么保护状态这比背十条监控规则都管用。