ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Redis持久化深度解析:RDB快照与AOF日志原理、对比及最佳实践

Redis持久化深度解析:RDB快照与AOF日志原理、对比及最佳实践 1. 先说结论到底什么是 Redis 持久化如果你用过 Redis大概率遇到过这种场景业务跑得好好的数据也在内存里躺着结果某天服务器重启或者 Redis 进程意外挂了重启之后发现——数据丢了不少甚至全没了。很多第一次接触 Redis 的人会惊呼“Redis 不是数据库吗数据怎么还会丢”。这里要澄清一个关键认知Redis 定位是一个基于内存的键值存储系统默认情况下它的所有数据都只存在内存里。内存的读写速度极快、性能极好但有个致命的弱点——断电或者进程退出数据就烟消云散。持久化机制就是为了弥补这个短板而存在的。所谓持久化就是定期或者实时地把内存里的数据写入磁盘文件。这样即使 Redis 进程崩溃、服务器断电重启之后也能从磁盘文件里把数据恢复回来。理解了这一点你才能真正明白 RDB 和 AOF 这两个机制到底在解决什么问题。在 Redis 的整个知识体系里持久化属于“保命”级别的基础能力。无论是自己搭单机环境做项目还是维护一套生产环境的 Redis 集群持久化策略的选型和配置直接决定了数据的可靠性。我等下会把 RDB 和 AOF 两种机制从原理、触发方式、优缺点、数据恢复、踩坑经验到生产环境的选型策略完整讲一遍尽量让你读完就能用上。2. RDB 快照持久化把内存“拍照”存下来RDB 的全称是 Redis DataBase它做的事情可以用一个很直观的词概括——快照。就像你给内存里的整个数据集拍了一张照片然后把这个照片以二进制文件的形式存到磁盘上。一旦需要恢复数据直接把这张照片重新加载回内存就行。2.1 RDB 的触发方式与配置细节RDB 的触发主要分三种手动触发、自动触发、以及关闭持久化时的一些边界情况。我先说配置再说原理。你打开 redis.conf 文件里面有一块经典的配置长这样save 900 1 save 300 10 save 60 10000这三行配置的含义是900 秒15 分钟内至少有 1 次写操作触发一次 RDB 快照300 秒5 分钟内至少有 10 次写操作触发一次60 秒1 分钟内至少有 10000 次写操作触发一次这里很多新手会理解错我特别强调一下这个规则不是“每隔 900 秒就存一次”而是“在最近 900 秒这个时间窗口内如果写操作次数累计达到阈值就触发一次”。如果你把 save 配置全部注释掉Redis 就完全不做 RDB 持久化——注意在没有任何其他持久化机制的情况下这意味着 Redis 只使用内存、不落盘重启即丢失所有数据。手动触发 RDB 有两个命令SAVE和BGSAVE。区别非常大。SAVE是同步操作Redis 主进程会阻塞在快照生成上期间所有客户端请求都无法处理。BGSAVE是异步操作Redis fork 出一个子进程去执行快照写入主进程继续对外提供服务。生产环境你基本只需要用BGSAVE。还有一个容易忽略的触发点当 Redis 通过SHUTDOWN命令正常关闭时如果配置了 RDB它会自动执行一次 RDB 持久化然后再退出。这意味着正常关机能保住数据但如果直接kill -9强杀进程那就只能根据最近一次自动触发或手动触发的 RDB 文件来恢复了。2.2 fork 背后的写时复制COW机制我刚才提到 fork 子进程来生成快照这里面的核心机制值得展开说因为它是 RDB 性能表现的根基。Redis 执行BGSAVE时主进程会调用系统fork()创建一个子进程。fork 之后的瞬间子进程和父进程共享同一份内存数据。此时如果父进程的写操作不多子进程直接读取共享内存并写入临时 RDB 文件就够了。但如果有新的写请求进来主进程会利用操作系统的**写时复制Copy-On-WriteCOW**技术把即将被修改的内存页复制一份出来再在副本上做修改。也就是说子进程看到的内存快照始终停留在 fork 那一刻的状态不会被后续写操作污染。这套机制保证了 RDB 生成期间主进程几乎不阻塞因为最耗时的磁盘写入都发生在子进程里。但代价是fork 本身会复制进程页表在大内存实例上 fork 会短暂阻塞主进程时间通常在几十毫秒到一两秒不等。另外在写操作频繁的场景下COW 会导致内存中共享页被大量复制内存占用会瞬时上涨。如果你的机器内存已经很紧张BGSAVE可能直接把内存顶爆触发系统 OOM——这是个非常隐蔽的坑。2.3 RDB 文件长什么样怎么恢复RDB 文件是一个二进制文件默认名字叫dump.rdb。它的结构主要由这几部分组成文件头包含魔数 REDIS 和版本号元数据RDB 版本、Redis 版本、创建时间等数据集逐条存储键值对包含类型编码字符串、列表、哈希等、过期时间等信息文件尾校验和用于检测文件完整性恢复过程不需要你手动干预。Redis 启动时会自动查找dir配置的目录下的dbfilename文件找到就加载。加载完成后你就能正常使用了。顺带说一个细节如果 redis.conf 里同时开启了 AOFRedis 启动时会优先加载 AOF 文件而不是 RDB 文件因为 AOF 在数据可靠性方面通常更优。只有当 AOF 关闭时才会去加载 RDB 文件。这个优先级关系在生产环境出问题时非常重要稍后我会专门讲。2.4 RDB 的优点和硬伤RDB 最大的优点一个是恢复速度快。RDB 文件是二进制紧凑格式Redis 加载时几乎是按字节解析直接重建数据结构比如在 4GB 的数据集上恢复时间通常只需要几十秒量级而同样数据量用 AOF 重放可能需要几分钟甚至更久。第二个优点是文件体积小。RDB 保存的是某个时间点的内存快照多个键可能被压缩存储文件远小于攒了很久日志的 AOF 文件方便做备份、上传到对象存储、下载到本地分析。RDB 的硬伤也很明显它无法做到数据的不丢失。因为快照是周期性的两次快照之间的写操作全部丢失。比如你配置的是save 300 10那么最坏情况下一场宕机会丢失最近 5 分钟的全部写入数据。对很多业务来说5 分钟的数据丢失是完全不能接受的——用户下了订单、支付了款项结果 Redis 一重启订单没了这种后果可以说非常严重。3. AOF 日志持久化把每次写操作都记下来如果说 RDB 是“结果导向”——直接存结果那么 AOFAppend Only File就是“过程导向”——把每一步写操作都记录下来。AOF 文件里存的是 Redis 协议的文本命令比如SET user:10001 Alice。恢复的时候把 AOF 文件里的命令从头到尾重放一遍数据就回来了。3.1 AOF 开启与三个刷盘策略AOF 默认是关闭的需要在 redis.conf 里手动开启appendonly yes appendfilename appendonly.aof这是基本配置。真正影响数据可靠性的是下面这个参数——appendfsync它控制日志内容写入磁盘的时机。有三个可选值always每条命令执行完成后立刻执行 fsync 强制刷盘。最安全一条数据都不会丢但性能最差因为每次写操作都要等磁盘落盘everysec每秒执行一次 fsync。最多丢失最近 1 秒的写入性能和可靠性达到不错的平衡no完全交给操作系统决定何时刷盘。性能最好但操作系统缓存强制刷盘前的任何数据丢了也就丢了不可控性强生产环境默认建议用everysec。always只适合对每条数据都极其敏感、且写入量不大的场景比如钱包余额之类的。说实话别轻易用always它会让你发现 Redis 的性能可能反而比不上普通关系型数据库。3.2 AOF 重写不控制文件大小会炸掉如果你开启了 AOF又长时间不干预日志文件会越来越大。比如你把一个值从 1 改成 2、从 2 改成 3、从 3 又改成 100AOF 文件会存三条SET命令。恢复时执行三条最终得到 100。但显然前两条是多余的因为最终结果只需要SET key 100这一条命令。为了解决文件膨胀问题Redis 设计了AOF 重写rewrite机制。重写并不是把日志文件变小那么简单它的逻辑是扫描当前内存中的完整数据集重新生成一套最少命令集合写到一个新的 AOF 文件里然后替换旧文件。比如刚才的例子重写后只会有一条SET key 100。重写有两个触发条件配置如下auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb含义是当 AOF 文件的体积比上次重写后的体积至少增长 100%即翻倍时并且 AOF 文件大于 64MB触发自动重写。这里的“上次重写后的体积”如果从未重写过则指 AOF 启动时的体积。这两个条件要同时满足才会触发单独满足一个没有用。重写的过程也值得一提。Redis 会 fork 一个子进程来做重写子进程扫描内存生成新的 AOF 内容。这个过程中如果有新的写命令进来主进程会把新命令同时写入一个重写缓冲区rewrite buffer待子进程完成后再把缓冲区里积累的命令追加到新文件的末尾保证新文件数据完整。执行BGREWRITEAOF命令可以手动触发重写。3.3 AOF 损坏与修复工具AOF 是文本文件理论上比 RDB 二进制文件更容易出现损坏问题——比如磁盘写了一半、空间不足导致截断。Redis 自带一个修复工具redis-check-aof使用方式很直接redis-check-aof --fix appendonly.aof它会把文件中损坏的尾部命令删掉尽量保留完整的命令。但这个操作有个不得不说的副作用修复完成后被截断掉的那部分命令对应的数据会永久丢失。所以建议修复前先备份原始 AOF 文件万一丢了重要数据还能再想办法。另外AOF 加载数据时会逐条解析命令并执行。如果文件里有语法错误Redis 启动阶段会直接报错拒绝启动。此时可以先启动修复工具再用修复后的文件启动。有些教程会让你直接把出问题的那条命令删掉再重启这在测试环境可以生产环境务必先备份。3.4 AOF 的优点和缺点AOF 最大的优点是数据更加安全。配置everysec最多丢一秒数据配置always一条不丢。对绝大多数业务来说这个可靠性已经足够了。另一个隐形优点是AOF 是可读文本你可以直接打开文件看到所有写操作记录便于排查问题甚至可以用grep之类的方式快速查询某些键的操作轨迹。它的缺点同样明显。首先是恢复速度慢。AOF 文件是文本协议恢复时要逐条执行命令远没有加载二进制 RDB 快照快。假设你已经攒了好几百 MB 的 AOF 文件Redis 启动可能要耗时好几分钟这段时间内服务完全不可用。其次是文件体积大。即使有重写机制兜底AOF 文件通常还是会比 RDB 大不少因为日志记录的是操作过程并且不会有 RDB 那种紧凑的二进制编码。还有一个很多人没注意到的细节AOF 开启时RDB 仍然可以作为冷备文件存在。也就是说它们不是互斥关系你完全可以两个都开。下一节我会展开讲。4. 全方位对比一张表看懂 RDB 和 AOF对比这两个机制不能只看表面区别得从底层原理、性能、可靠性、恢复速度、文件格式、适用场景等多维度逐个拆。我先给出一张总览表然后逐条解析背后的逻辑。对比维度RDB 快照AOF 日志文件格式二进制压缩快照文本协议命令数据恢复速度快直接加载快照慢需逐条重放命令数据丢失风险两次快照间的数据全部丢失最坏窗口取决于 save 规则everysec 最多丢 1 秒always 不丢文件体积紧凑相对小通常更大靠重写控制膨胀对主进程性能影响fork 时短暂阻塞COW 期间内存占用上升always 模式写操作性能严重下降是否可读不可读二进制可读能分析命令流是否有修复工具无统一工具损坏后基本无法恢复有 redis-check-aof 可修复复制主从场景通常作为主从复制的初始同步文件全量同步后增量数据靠 replbacklog 与 AOF 配合4.1 性能与可靠性的取舍逻辑RDB 的性能优势在于“批量写”它通过子进程把内存整体落盘主进程几乎不参与 IO。而 AOF 是“实时写”每一条命令都要经过写文件这个环节无论你怎么优化单次写入的延迟必然高于纯内存操作。在 p99 延迟敏感的业务里always模式的 AOF 会明显拉高写入延迟这是有过实际教训的。可靠性的逻辑正好相反。RDB 的快照周期决定了数据丢失窗口哪怕你把 save 调成save 1 1每秒有一次写就立刻保存fork 的成本也会让你得不偿失。AOF 的everysec和always则提供了更细粒度的数据保护把丢失窗口精准控制到一个命令或一秒。4.2 恢复时间的真实差距我用一个 8GB 数据集、普通 SSD 磁盘的环境实测过RDB 文件加载大约 30 到 50 秒完成AOF 用默认everysec外加每 128MB 自动重写文件大约 5GB恢复时间基本要 3 到 5 分钟。这中间的差距对高可用场景影响极大——Redis 故障恢复期间整个缓存层都处于空窗期数据库压力直接翻倍甚至被打崩。这也是为什么很多 Redis 服务端架构在做故障恢复时会优先选择“先加载 RDB 快速提供基本服务后台再异步重建并切到 AOF”这种复杂策略。当然这是中间件层面才需要解决的问题单机场景下你意识到恢复速度的差距就够了。4.3 混合持久化Redis 4.0 的集大成方案很多人不知道Redis 4.0 开始支持了混合持久化RDB AOF。这个方案完美结合了两者的优点。开启方式aof-use-rdb-preamble yes开启后AOF 文件的结构变成以 RDB 格式开头后续再追加 AOF 命令。也就是说Redis 启动加载时先读取 AOF 文件中的 RDB 部分数据量大的这部分恢复速度接近 RDB再重放后面的增量命令数据量小、速度快。这样既解决了恢复慢的问题又保证了数据不丢失。这是目前生产环境最推荐的持久化组合。要注意的是混合持久化并不是独立于 AOF 的第三种机制它本质上是 AOF 文件格式的改良。所以使用它仍需要appendonly yes。5. 生产环境选型与最佳实践讲清楚原理之后落到实际选型我把自己做过的项目里踩过的坑和总结出的策略一次说清楚。5.1 不同业务场景下的配置建议场景一缓存型业务允许少量丢失典型的场景是 Session 缓存、热点数据缓存。就算 Redis 重启丢了最近几分钟的数据数据库里还有原始数据可以从 DB 重新加载到缓存。这种场景可以只开启 RDB配置save 900 1或更宽松的规则甚至完全关掉持久化都行纯内存缓存。场景二数据库型业务数据不能丢比如把 Redis 当库存、余额、订单状态这类关键数据存储来用。这种场景必须开 AOF并且建议appendfsync everysec。如果你想做到一条不丢用always但要评估写入性能是否扛得住。同时保留 RDB利用混合持久化模式恢复时才不遭罪。场景三大数据量、高可用敏感场景推荐开启混合持久化配置appendonly yes aof-use-rdb-preamble yes再设置合理的 AOF 重写触发阈值。这样做到数据不丢、恢复快、文件不至于无限膨胀。5.2 我认为很关键的三条配置细节第一RDB 的save规则不要设置得太激进。很多人为了“更安全”把 save 写成save 60 10结果发现磁盘 IO 变得很高fork 也非常频繁反而拖垮了主进程。每多一次 fork阻塞时间可能增加几十毫秒在高 QPS 业务下这是不可接受的。第二AOF 重写期间记得监控磁盘空间。AOF 重写会先生成一个新文件再替换旧的所以磁盘上短时间会存在两份 AOF 文件的体积之和。如果磁盘空间只有 30GBAOF 已经有 28GB重写直接失败甚至可能因为没有临时空间导致写操作失败。这个坑一定要提前留足磁盘余量。第三备份文件不要只放在本机。无论 RDB 还是 AOF本机磁盘挂了你什么都找不回来。正经做法是配合定时任务把 RDB 文件压缩后传到对象存储或者异机备份目录。恢复时从备份拉回文件再手动放到 Redis 的dir目录下。5.3 关闭持久化的正确姿势有些性能敏感型业务会故意关闭持久化让 Redis 变成一个纯内存缓存。配置方法是把save全部注释掉并且appendonly no。但我遇到过有人只注释了 save却忘了 AOF 还开着结果日志越攒越多磁盘直接打满。关闭持久化时务必确认以下全局配置都到位save 或者全部注释 save 规则appendonly no重启 Redis 前确认没有残留的dump.rdb或appendonly.aof文件否则启动时还是会尝试加载另一件要注意的事即使关闭了持久化如果你用SAVE手动执行一次Redis 仍然会把数据写入 RDB 文件。所以运维脚本里如果用了SAVE实际上还是会产生磁盘文件。6. 常见问题与排查技巧实录这块我把实际遇到过的问题整理成一个速查表一些偏门的“坑”也一并列出来。6.1 速查表现象可能原因解决方案AOF 文件损坏导致 Redis 无法启动非正常停机、磁盘写入中断运行redis-check-aof --fix修复RDB 文件损坏服务起不来磁盘写满、IO 异常导致快照写入不完整从备份恢复RDB 无自动修复持久化目录磁盘满写操作报错dir指向的分区空间不足清理日志、扩容、或者更改持久化目录fork 操作导致主进程卡顿实例内存过大fork 复制页表耗时长错峰执行 BGSAVE或考虑集群切片BGSAVE 后内存瞬间上涨很多COW 机制复制了大量内存页错峰操作适当缩小单实例内存AOF 文件疯狂增长重写失败 / 重写条件未触发手动BGREWRITEAOF检查磁盘余量启动时加载了旧数据新数据丢失持久化触发频率不够调整 save 规则开启 AOF 并提高 fsync 频率6.2 “丢了 30 分钟数据”的经典事故分析有一次我们生产环境的 Redis 挂了恢复后客户反馈缓存里的数据普遍是旧了半小时的。查下来原因很典型业务方只开了 RDB配置还用的是默认save 900 1也就是最坏丢 15 分钟——但因为那段时间写入量还没达到阈值加上服务器突然断电实际丢了 30 多分钟。这就是 RDB 的天然短板叠加配置不当的双重结果。后来我们把持久化策略改成了appendonly yes appendfsync everysec aof-use-rdb-preamble yes再配合 RDB 定时备份。从那以后类似的事故再没有出现过。这件事给我最大的教训是当你不确定业务能容忍丢多少数据时用 AOF 兜底永远是更稳的选择。6.3 关于 RDB 文件加载失败的一个偏门问题有一种情况你可能想不到RDB 文件本身没问题但 Redis 启动时加载失败错误日志里提示“short read or OOM loading DB”。这通常不是文件损坏而是机器内存不足以容纳整个数据集。RDB 加载是直接把数据集全部载入内存的如果物理内存不够加载过程就会失败。解决办法很直接给机器加内存、或者启用 Redis 的虚拟内存机制不推荐、或者考虑混合持久化和集群拆分。7. 写在最后也是我最想告诉你的一件事如果你只能记住这篇文章里的一个结论我希望是永远不要把持久化当成“开没开”的问题来看而要当成“数据能丢多少、恢复要多快”的权衡题来设计。RDB 和 AOF 没有绝对的谁好谁坏——RDB 恢复快、体积小但数据丢失窗口大AOF 数据安全、可读可修但恢复慢、文件大。最理想的方案就是让它们在混合模式下各司其职RDB 负责快速重建数据底子AOF 负责补齐增量、把丢失窗口压到秒级。我建议各位可以试着在自己的测试环境里做一次“破坏性实验”开两个 Redis 实例一个只配 RDB一个配 AOF everysec分别写入同样多的数据然后直接kill -9杀掉进程再重启看看数据差异。这种亲手操作比看一百篇对比文章都管用。等你真正在日志里看到那几行“Missing N changes in the last M seconds”的警告你就彻底明白为什么要重视持久化了。
返回列表