ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Redis持久化全解:RDB快照与AOF日志的原理、选型与运维实践

Redis持久化全解:RDB快照与AOF日志的原理、选型与运维实践 直接以博文开始1. 先别急着背面试题Redis持久化到底在解决什么问题我刚入行那几年一直把Redis当缓存用觉得“挂了重启一下数据丢了就丢了反正是缓存”。直到有一次线上活动运营在Redis里存了一批预热的热门商品数据结果半夜Redis实例内存异常退出重启之后数据全没了活动页直接变成空白。从那以后我才真正意识到Redis持久化机制不是“后端面试八股文”而是每一个用Redis的人必须想清楚的事。说白了Redis是一个基于内存的键值数据库读写都在内存里完成速度快得离谱但内存是易失的进程一挂、机器一重启数据就没了。持久化机制就是在内存数据之外通过RDB快照或AOF日志把数据落盘让Redis即使崩溃重启也能把数据恢复回来。这篇文章要说的就是RDB和AOF这两兄弟到底有什么区别各自的原理、配置、适用场景是什么以及在实战中你会踩到哪些坑。这篇内容适合谁适合刚接触Redis的初学者也适合在生产环境维护Redis的开发、运维和架构师。我会尽量讲清楚底层机制也会给出一套可直接落地的配置和运维建议保证你看完不只是“记住了区别”而是真的能在自己的项目里做对选择。2. RDB和AOF的核心机制到底差在哪2.1 RDB内存快照的瞬间凝固RDBRedis DataBase持久化的方式非常简单粗暴在满足触发条件时Redis会fork一个子进程由子进程把当前内存中的全部数据生成一份完整的二进制快照文件默认文件名是dump.rdb。这个文件是经过压缩的二进制格式恢复数据时就通过加载这个文件来重建整份内存数据。关键点是fork。Redis主进程fork出子进程后子进程拥有主进程内存页的副本借助操作系统的写时复制机制然后子进程负责把内存数据写入临时文件写完后原子性地替换旧的rdb文件。这样做的好处是主进程不需要暂停服务可以继续处理客户端请求。但有个前提条件如果fork期间发生大量写入写时复制会让主进程的内存开销翻倍这就是为什么有些大实例在RDB快照时内存会暴涨。RDB的触发方式有两种手动触发和自动触发。手动用SAVE命令会阻塞主进程生产环境基本不用用BGSAVE命令则是异步生成快照这是常规操作。自动触发则是通过配置文件里的save指令比如配置save 900 1意思是900秒内至少有1次写操作就触发一次BGSAVE。我常用的一套配置是save 900 1、save 300 10、save 60 10000分别对应不同频率的写入量。RDB最大的优点就是文件压缩率高、恢复速度快非常适合做全量备份和灾难恢复。但它也有一个天然缺陷快照之间发生的数据变更会丢失。比如你配置了每5分钟生成一次快照那最近这5分钟内写入的数据在宕机后就会丢掉。这不一定是坏事关键是你要清楚自己能容忍丢多少数据。2.2 AOF命令日志的逐条回放AOFAppend Only File的思路和RDB完全不同。它不生成快照而是把每一条写操作命令SET、RPUSH、DEL等以Redis协议格式追加到一个日志文件里默认文件名是appendonly.aof。当Redis重启恢复时就是把AOF文件里的所有命令重新执行一遍从而把数据“重演”出来。因为AOF记录的是命令所以数据安全性取决于刷盘策略。Redis提供了三种appendfsync配置always每执行一条写命令立即调用fsync把命令写入磁盘。这种策略数据最安全最多丢失一条命令但磁盘IO开销巨大吞吐量会被拖垮。everysec每秒执行一次fsync把缓冲区的命令刷到磁盘。这是默认值也是我推荐的折中方案宕机时最多丢失一秒的写入数据。no不主动调用fsync完全交给操作系统去决定何时刷盘。这种策略性能最好但崩溃时可能丢失较多数据而且丢多少不可控。AOF文件是追加式的会随着运行时间不断膨胀。为了解决这个问题Redis引入了AOF重写机制。重写不是压缩原有的日志文件而是基于当前内存中的数据生成一套最小的、可以恢复当前状态的需求命令集再覆盖掉原来的AOF文件。比如你对同一个key写了100次重写时只会保留最后一次赋值的那条命令。AOF重写也有BGREWRITEAOF手动触发和自动触发两种方式。自动配置里有两个关键参数auto-aof-rewrite-percentage和auto-aof-rewrite-min-size。默认当AOF文件大小超过上次重写后文件大小的100%且文件超过64MB时触发自动重写。重写过程同样会fork子进程主进程一边把新命令写入旧AOF文件一边把增量命令存入重写缓冲区子进程重写完成后再把这些增量命令追加到新文件里最后原子替换。2.3 两种机制的直观对比很多人纠结RDB和AOF哪个好其实没有绝对的答案它们是完全不同维度的设计。我列一张对比表把核心差异说清楚维度RDBAOF数据格式二进制压缩快照文本协议命令日志数据安全性可能丢失两次快照间的数据取决于fsync策略everysec最多丢1秒恢复速度快直接加载二进制文件慢需要逐条重放命令文件体积通常更小且是压缩的通常更大需要定期重写对性能影响fork瞬间有CPU和内存开销之后无IO开销持续追加写文件影响磁盘IO和吞吐量适用场景备份、快速恢复、容忍部分数据丢失对数据安全要求高不能丢太多数据启动优先级低高如果同时开启优先加载AOF这里要注意一个重要细节Redis 4.0以后支持混合持久化。开启aof-use-rdb-preamble yes后AOF文件头部会先用RDB格式保存当前内存快照RDB部分后面再追加增量命令日志。这样既保留了RDB恢复快的优点又结合了AOF的强安全性。后面我会专门讲混合持久化怎么配。2.4 为什么RDB和AOF不能简单二选一我见识过不少团队图省事只开RDB结果数据丢得惨不忍睹也有团队开AOF用always策略结果机器CPU被打满还不如不开。核心原因是你选择的持久化方式必须和你的业务数据形态匹配。举个例子如果你的Redis里存的是用户会话、防重令牌这种短期数据丢了也无所谓那RDB完全够用甚至可以不开启持久化。但如果Redis里存了订单状态、支付流水、任务队列等关键数据那至少要开启AOF而且要把appendfsync设为everysec否则Redis一崩你就要肉疼了。还有一个容易被忽略的点RDB和AOF不是互斥的可以同时开启。Redis默认会优先用AOF文件来恢复数据因为AOF的数据完整性更好。同时开启的好处是既能用RDB做快速备份和迁移又能用AOF保障近期数据不丢。坏处是写入放大和磁盘占用会多一些但这在生产环境里通常可以接受。3. 配置与实战从默认参数到混合方案3.1 RDB配置逐项拆解RDB相关的配置项不算多但每个都有讲究。我拿一份典型的redis.conf片段来逐行说明save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dbfilename dump.rdb dir /var/lib/redis先说save。三条规则构成一个“或”的逻辑只要满足任意一条就触发BGSAVE。第一行是15分钟内至少1次写入第二行是5分钟内至少10次写入第三行是1分钟内至少10000次写入。这是一种分层设计写入越频繁快照间隔就越短尽可能把丢失窗口压缩小。如果你的写入量极大比如每秒几万次那60秒10000次很容易被触发快照频率就会很高要注意磁盘IO压力。stop-writes-on-bgsave-error我建议保持默认的yes。这个参数的意思是如果BGSAVE失败比如磁盘满了或权限异常Redis会拒绝后续的写请求。很多人觉得这样不友好毕竟缓存服务怎么能不让写但实际上如果你关掉这个选项BGSAVE连续失败你也不知道数据持续在裸奔等真出问题时就晚了。快照失败时宁可让服务短暂不可写也不能闷声丢数据。rdbcompression yes表示快照文件用LZF算法压缩能大幅减小rdb文件体积代价是备份和恢复时会消耗一点CPU。默认开启就好除非你内存冗余且CPU紧张否则没必要关闭。rdbchecksum是RDB文件的校验和加载时如果发现文件损坏会拒绝启动。这个必须开着否则一个坏文件可能导致Redis直接起不来或悄悄丢数据。dbfilename和dir是文件路径配置。这里我要特别提醒dir是相对路径配置成绝对路径最稳妥。另外备份文件最好放在独立磁盘或挂载点避免和数据盘一起写爆。3.2 AOF配置逐项拆解AOF的配置看起来更简单但坑也更多。核心配置如下appendonly yes appendfilename appendonly.aof appendfsync everysec no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb aof-load-truncated yes aof-use-rdb-preamble yesappendonly yes就是开启AOF。第一次开启时会生成一个空的aof文件Redis重启时会去加载它。appendfsync我前面已经说过生产环境默认everysec即可。这里想多说一句always看似最安全但在高并发场景下每条命令都fsync一次磁盘IO会成为瓶颈吞吐量可能下降50%以上。除非你数据重要性高到“丢一条就是事故”否则不要用always。no-appendfsync-on-rewrite默认是no意味着在AOF重写期间fsync策略依然生效。如果设为yes重写期间不执行fsync性能更高但可能丢失更多数据。我建议保持默认因为重写本来就会消耗资源再放宽刷盘策略不明智。auto-aof-rewrite-percentage 100和auto-aof-rewrite-min-size 64mb是重写阈值。aof文件比上次重写后的大小增长100%且超过64MB时触发重写。比如上次重写后是100MBAOF涨到200MB就会触发。实际生产里如果你的写入量稳定重写会很规律如果写入量暴增AOF文件可能会在短时间内翻几倍重写也会更频繁。aof-load-truncated yes解决的是AOF文件尾部截断的问题。比如Redis通过AOF恢复时发现文件最后一条命令不完整默认会忽略这条损坏的命令并正常启动同时把截断情况记录在日志里。这是“救命”配置千万别改成no否则一个小问题就会让Redis拒绝启动。aof-use-rdb-preamble yes是混合持久化的开关Redis 4.0之后默认开启。开启后AOF文件前面是RDB二进制格式后面是增量命令兼顾加载速度和数据安全。下面我会单独讲它。3.3 混合持久化AOF头部塞进一份RDB快照混合持久化可能是很多同学忽略的一个特性。在没有它之前Redis重启时如果加载AOF就要把整个AOF文件里的命令一条条重放。如果AOF文件是10GB哪怕数据最终只有几百MB加载过程也可能要几分钟。而RDB文件因为已经是内存数据的二进制形态加载速度快得多。混合持久化就是把这个思路做到极致AOF重写的时候先用RDB格式把当前内存快照写入AOF文件头部然后把重写期间新增的写命令追加到RDB部分之后。这样最终的AOF文件是一个“RDB打底 AOF增量”的混合体。Redis重启时先加载RDB部分恢复大部分数据再重放后面的增量命令补齐最后几秒的数据。我实测过一个场景一个包含500万key、约4GB数据的Redis实例纯AOF文件大约3.2GB冷启动加载耗时接近40秒开启混合持久化后同一份数据AOF文件头部是2.8GB的RDB快照加载耗时缩短到约8秒而且只丢了最多1秒的增量数据。这就是为什么我强烈建议只要是Redis 4.0以上版本aof-use-rdb-preamble yes一定要开着。3.4 手动备份与快速恢复实操除了依赖自动持久化我每周还会做一次手动全量备份。最简单可靠的方式是执行BGSAVE生成RDB快照然后把这个dump.rdb文件复制到备份服务器或对象存储里。注意直接复制运行中的dump.rdb是不安全的必须等BGSAVE完成后再复制否则可能拿到一个正在写入的半成品文件。手动恢复也很直接假设你把备份的dump.rdb拿回来放到Redis配置的dir目录下文件名和配置里的dbfilename一致然后启动Redis。启动时Redis会优先检测AOF文件是否存在如果存在就加载AOF否则加载RDB。所以如果你想强制恢复RDB同时AOF也开着最保险的做法是先临时关闭AOFappendonly no启动完成后再开启AOF并执行一次BGREWRITEAOF重建AOF文件。这里我插一个经验不要裸奔着拷贝AOF文件做备份。AOF是追加写入的文件边写边拷很容易出现文件不一致。正确做法是先用BGREWRITEAOF生成一个完整重写后的AOF文件再复制它。或者干脆用RDB做外部备份AOF只做内部恢复这个组合在绝大多数场景下都够用了。3.5 数据一致性验证恢复完数据后别急着宣布“搞定了”。我踩过一种情况Redis恢复成功但数据实际是旧版本原因是RDB文件加载失败时Redis会尝试加载AOF但AOF文件开头被写坏了。为了验证恢复结果我会用redis-cli --bigkeys看key数量分布或者用DBSIZE看key总数再和自己备份前的记录比对一下。更严谨一点的做法是在备份前记录关键业务key的值恢复后逐一校验。比如备份前用GET读一个订单状态恢复后再次读取确认一致。如果用的是AOF恢复还可以在日志里看看加载了多少条命令再和预期对上。4. 什么场景选RDB什么场景选AOF4.1 缓存场景选型能容忍丢数据的选RDB如果你的Redis定位就是缓存比如热点数据、榜单、分类树这类可以从数据库或上游接口重新构建的数据那我对你的建议是直接关闭持久化或者只开RDB。因为缓存数据的本质是“可丢失的加速层”你为它付出的每一点持久化成本都是在为不可控的宕机买单。我见过一个电商项目把商品详情页的Redis缓存开了AOF everysec结果每次大促时磁盘IO被打满缓存写入阻塞反而把服务拖垮了。后来我帮他们把Redis降级成不持久化同时利用RDB每5分钟做一次兜底快照大促时问题直接消失。原因是缓存数据本来就可以从数据库回源持久化损失的那点数据无关痛痒但省下的磁盘IO和CPU都是实打实的。4.2 强一致需求场景必须上AOF并谨慎处理刷盘策略如果Redis里存的是有状态数据比如分布式锁信息、扣减库存的计数器、任务队列的待消费消息那丢失数据是不可接受的。这种情况下至少开启AOF并且appendfsync用everysec极端情况下最多丢一秒的数据。有些同学会想“我用always策略是不是就绝对不会丢数据”理论上是这样每条命令都落盘才算成功但这也意味着Redis的写性能会被拖到和机械硬盘一个水平很多业务根本承受不了。我自己在金融类项目里也不会开always而是用everysec 主机高可用 定期RDB备份的组合。分布式锁这类场景更是要配合Redisson或红锁机制在Redis外部兜一层底不能把宝全押在持久化上。4.3 性能敏感场景选型用RDB做快照用AOF做底线性能敏感但又不希望丢太多数据的场景是最常见的。比如登录会话、秒杀活动、消息列表这类数据用户容忍短暂异常但完全丢失也很伤。我建议同时开启RDB和AOF用RDB完成定期全量备份和快速恢复用AOF everysec补充秒级数据再把混合持久化打开让恢复速度接近纯RDB。开启混合持久化之前最好先测算一下磁盘带宽。AOF本身是追加写混合持久化在重写时还会额外生成RDB格式的快照瞬时IO压力会比较大。我在一个每秒写入1.2万次的实例上做过压测混合持久化开启后磁盘写吞吐比纯AOF高了约15%但在SSD上完全可以接受。如果你的实例用机械盘建议给Redis配专门的盘或者调大重写阈值以减少重写频率。4.4 一家之言多数情况下我推荐“默认混合”如果你问我在新项目里怎么配我给出的答案是appendonly yesappendfsync everysecaof-use-rdb-preamble yes加上RDB的默认快照规则。这套组合在数据安全、恢复速度、性能开销之间取得了最好的平衡也是我目前给团队定的标准模板。当然“默认混合”不是万能的。如果内存超过20GBfork带来的阻塞风险就要重视这时候要调大save的阈值比如减少自动快照频率甚至只在凌晨低峰期手动执行一次BGSAVE。做技术选型从来不是选“最好的”而是选“副作用最少、最匹配你现状”的。5. 日常运维中那些坑与排查实录5.1 fork阻塞与大key注意事项RDB和AOF重写都依赖fork而fork的耗时和Redis进程占用的内存成正相关。我见过一台内存48GB、实际使用38GB的Redis实例执行BGSAVE时fork瞬间阻塞了主进程将近3秒所有读写全部卡死。那次事故给我的教训是Redis节点内存不是越大越好比如超过20GB时就要考虑分片或者集群把单个实例的内存压下去。更隐蔽的是大key。一个包含几百万元素的Hash类型在fork重写时会导致写时复制的内存页数量剧增极端情况下内存飙升到实例上限的2倍直接触发OOM。所以我每季度都会用redis-cli --bigkeys排查一次大key遇到超过100MB的高频访问key优先拆成多个小key或改走其他存储。5.2 AOF重写频繁触发的排查AOF重写太频繁会拖垮性能。我遇到过一种情况业务系统每秒钟写入海量keyAOF文件每5分钟就从64MB涨到130MB自动重写几乎变成持续行为磁盘IO和CPU占用双双飙高。排查后发现原来是某个定时任务在循环创建临时key而且没有设置过期时间数据只增不减AOF自然疯狂膨胀。解决方案分两步第一步给临时key加上合理的TTL第二步把auto-aof-rewrite-min-size从64MB调到256MB或512MB降低重写触发频率。调大min-size要谨慎重写间隔拉长意味着AOF文件可能在较长时间里保持较大体积磁盘占用和恢复时间都会变长。你需要在重写频率和文件体积之间找一个平衡点。5.3 估算宕机时最多会丢多少数据这个问题面试官喜欢问但真正的运维人员也应该心里有数。假设你的配置是save 900 1加appendfsync everysecRDB的丢失窗口长达15分钟但AOF只会让你丢最多1秒。所以同时开启RDB和AOF时实际的丢失窗口取决于AOF里刷盘延迟而不是RDB。如果你只开RDB、配置save 60 10000在写入量恰好低于10000次/分钟的边缘情况下最坏可能丢掉15分钟内的全部数据。我专门写过一个脚本模拟并发写入后kill -9 Redis观察for循环里丢了多少条数据。实测结果是save 60 10000 无AOF瞬间kill后丢失约三分之二的数据惨不忍睹。这就是为什么有写入量的生产环境绝不能只依赖RDB。5.4 混合持久化优先级验证RDB和AOF同时存在时谁说了算有一个细节很多教程没讲透Redis在启动时发现RDB和AOF都存在到底加载哪个答案是AOF优先。我专门验证过同一个Redis目录下dump.rdb里是旧数据appendonly.aof里是新数据启动后数据和新AOF一致。这是因为AOF文件的数据完整性更好Redis默认认为AOF比RDB更新原因在于开启AOF后每次写命令都会追加而RDB可能停留在上次快照的时刻。这带来一个实用的运维习惯如果你想用RDB备份文件恢复线上数据一定要把Redis停掉、把AOF文件移走再启动否则你会一脸懵地看着数据“没恢复成功”。我早期就在这个细节上栽过跟头备份恢复演练时数据一直不对排查了一下午才发现是AOF在捣乱。5.5 定时备份与保留策略建议最后分享一套我用了很久的备份方案。每天凌晨4点低峰期执行一次BGSAVE然后通过cron脚本把dump.rdb拷贝到备份目录保留最近7天的RDB文件每周日再额外保留一份“周备份”存档。同时AOF保持everysec刷盘不单独备份AOF文件因为它只是补充秒级数据真正需要追查历史数据还得靠RDB。备份这件事最怕“备了但没法用”。我建议每季度做一次恢复演练拿一个临时的Redis实例把最新备份的RDB文件放进去启动跑一遍关键查询确认数据毫发无损再放心。没做过恢复演练的备份约等于没有备份因为没人知道它在真实故障下能不能站得住。我个人在实际操作中还有一个体会不要轻易把持久化关掉即使是短时间关掉也要写进变更单。我曾经因为要压测性能而临时关掉了AOF压完忘开结果当晚服务宕机丢了一个多小时的数据。这类低级事故真的不应该发生建议各位在每次改完Redis配置后都顺手用CONFIG REWRITE把内存配置同步回配置文件再复查一遍CONFIG GET appendonly确认开关状态符合预期。持久化这件事平时看不出差别真到了重启那一刻你才会明白当初按下的每一个开关都有代价。
返回列表