ARTICLE DETAIL

资讯详情

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

Redis持久化全解析:RDB与AOF原理、备份恢复实战指南

Redis持久化全解析:RDB与AOF原理、备份恢复实战指南 1. 一开始先聊聊Redis持久化到底是什么事在绝大多数公司里Redis被当作缓存用重启丢了数据似乎也没啥大不了顶多把数据库再灌一遍。但等你碰到“缓存”里存着的是用户会话、秒杀库存、未落库的排行榜数据或者干脆就是把Redis当数据库用的场景重启丢数据就是事故级别的问题了。我自己接过几次这种线上告警清一色是“Redis重启之后数据少了一半”“主库切换后写入的数据没了”。所以聊“Redis 持久化、备份、还原”本质上聊的是两件事第一进程退出或机器宕机后内存数据怎么不丢第二数据真丢了或者想迁移环境怎么恢复。持久化解决前者备份还原解决后者。很多人把它们混为一谈实际上持久化做得好只能保证“正常崩溃”不丢数据误操作删key、跑错命令、硬盘损坏、机房断电烧了机器都得靠备份兜底。这篇文章不绕弯子直接讲清楚RDB和AOF两个持久化机制的原理、配置参数怎么调、备份脚本怎么写、还原有哪些坑最后附上我这几年代维攒下来的故障案例和排查技巧。适合刚接触Redis没多久、搞不清RDB和AOF区别的新人也适合已经被“持久化总是出问题”折磨过一轮、想系统性补课的运维和开发。2. 持久化机制拆解RDB与AOF两套方案各自的门道2.1 RDB快照全量落盘的写时复制机制RDB就是“定期把内存里的全量数据拍一张照片序列化后存到磁盘文件”。触发方式分为手动和自动两种手动是执行SAVE或BGSAVE自动是配置了save m n规则比如save 60 1000表示“60秒内如果有1000次写操作就触发一次快照”。这里最核心的原理是BGSAVE的实现方式。Redis会fork出一个子进程来执行快照写入父进程继续对外服务。子进程使用的内存页是fork瞬间的“冻结快照”通过操作系统的写时复制Copy-On-Write机制父进程后续对内存的修改不会影响子进程正在生成的RDB文件而是复制一份被修改的内存页给父进程自己用。这个机制的好处是快照不会阻塞主流程坏处是fork的瞬间如果内存很大、写入很频繁复制内存页会带来短暂的额外内存消耗极端情况下可能达到原有内存的数倍。我在生产环境见过最典型的一次踩坑实例内存接近20GB触发BGSAVE时服务器物理内存只剩几GBfork之后COW瞬间把内存打满直接触发OOM Killer把Redis进程杀了。所以RDB方案不是无脑配置大内存机器就能跑的你需要评估fork开销官方建议单机Redis内存最好不要超过可用物理内存的一半留出COW的空间。另外SAVE这个命令是同步阻塞的生产环境基本不用只有极少数需要立刻落盘的特殊操作才会允许它跑一次。RDB文件的优势是紧凑、恢复快、适合做全量备份的基座劣势是快照之间有间隔间隔期内写入的数据在故障时会丢。如果你想降低丢失窗口只能缩短save规则间隔但频繁fork又会带来性能损耗这就是RDB方案天生的权衡。2.2 AOF日志每条写命令按策略刷进磁盘AOF走的是另一个思路把每次修改数据的命令以Redis协议格式追加到日志文件重启后重放这些日志就能把数据找回来。它的核心参数是appendfsync控制的是操作系统的写缓冲多久真正刷到磁盘上always每个写命令执行后都调用fsync强制落盘最安全但性能损耗最大实测通常会比everysec慢一个数量级。everysec每秒批量fsync一次。最多丢一秒写入的数据性能与安全平衡得好绝大多数业务场景的首选。no把fsync时机完全交给操作系统通常30秒左右才刷一次丢数据最多一般不建议。everysec之所以成为事实标准是因为它把“丢数据窗口”压缩到一秒以内同时批量刷盘能摊薄IO开销。但注意一个细节即使设置了everysec如果Redis进程直接崩溃不是整机宕机内存里还没刷盘的命令会丢如果是整机断电则可能丢一秒甚至更多。想要“零丢失”只有always代价是每条命令都等磁盘返回写吞吐直接受限。我见过有些团队为了追求安全把appendfsync设成always结果高峰期写入毛刺严重还不如把丢失窗口妥协同到业务能承受的范围内。AOF还有一个aof重写机制就是当AOF文件越来越大时Redis会fork子进程把当前内存数据压缩成一条条最小的命令集合替换掉旧的冗余日志。比如原本对一个key做了100次递增重写后只剩一条INCRBY key 100。这个重写过程和RDB的fork一样有内存翻倍风险配置上要留意auto-aof-rewrite-percentage和auto-aof-rewrite-min-size两个参数默认是文件增长超过100%且大于64MB时触发实际生产环境建议根据自己的写入量收敛一下避免它在业务高峰自动触发。2.3 混合持久化Redis 4.0以后的重点关注方案Redis 4.0引入了混合持久化用一句话概括就是“RDB全量快照 AOF增量日志”的组合。开启方式是在redis.conf里设置aof-use-rdb-preamble yes。启用后AOF文件的开头先存储一份RDB二进制全量快照之后追加这段快照之后产生的写命令日志。重启时Redis先加载开头的RDB快照再重放后面的AOF增量恢复耗时远小于纯AOF丢失窗口又远小于纯RDB。这个方案几乎没什么短板既有RDB的加载速度又有AOF的精度。对大多数应用来说我建议直接开启混合持久化并把appendfsync设为everysec。但有个前提Redis版本必须4.0以上低版本升级之前要留意兼容性。另外使用了混合持久化之后备份文件不再是纯粹的单格式文件有些第三方工具如老版本CacheCloud无法直接解析这种混合文件需要先确认你的监控和管理工具链跟得上版本。2.4 RDB和AOF到底该怎么选很多人问“两者选哪个”其实现在的Redis早就不建议非此即彼了。我按场景给个归类纯缓存、对丢失窗口容忍度高的场景可以只开RDB配置宽松一点省IO开销。当数据库用、或缓存着订单、余额、库存这类敏感数据的场景必须开AOF并且搭配混合持久化。两种同时开启也可以Redis重启优先用AOF恢复数据因为AOF更完整RDB则作为备份、迁移、从节点初始化的辅助文件。一句话总结我的经验能开混合就开混合能开AOF就开AOF除非你的场景对IO极其敏感或数据丢了也无所谓否则别迷信“Redis只是缓存所以不配持久化”这种偷懒思路。3. 配置怎么做持久化参数的合理姿势3.1 redis.conf里真正需要盯紧的几个参数打开Redis的配置文件关于持久化的配置项其实不多但每一项背后都有讲究。我逐一列出来并说明生产环境上的推荐值配置项默认值推荐值说明save3600 1 300 100 60 10000按业务调整自动触发RDB的规则多个规则是或关系满足任意一条就触发stop-writes-on-bgsave-erroryesyesBGSAVE失败时是否停止写入防止磁盘满导致数据全丢时还继续接受写入rdbcompressionyesyesRDB文件是否压缩压缩能减少磁盘占用缺点是生成时多耗CPUrdbchecksumyesyesRDB文件末尾的CRC64校验和别关能提前发现文件损坏appendonlynoyes是否开启AOFappendfilenameappendonly.aof按需修改AOF文件名多实例部署时建议区分appendfsynceveryseceverysecfsync策略特殊需求才用alwaysauto-aof-rewrite-percentage100100或更小AOF文件比上次重写基准增长多少触发重写auto-aof-rewrite-min-size64mb按实际调整文件超过该尺寸才允许自动重写aof-use-rdb-preambleyes4.0后默认开启yes混合持久化开关几个容易被人忽视的细节第一stop-writes-on-bgsave-error默认是yes意思是如果BGSAVE失败最常见的是磁盘满了Redis会主动拒绝所有写入请求。很多人遇到“Redis突然写入报错”一脸懵查下来是这条保护机制在起作用。这个配置建议保持默认除非你有完善的告警体系能第一时间感知持久化失败否则别轻易改成no。第二save规则不是越多越好。我见过有人配置了save 1 1意思是每次写入都触发RDB快照然后发现线上CPU和磁盘都被fork拖垮了。自动快照触发越频繁潜在掉电丢失窗口越小但fork代价越大。比较务实的做法是主节点配置相对宽松的规则比如save 900 1、save 300 10、save 60 10000然后靠从节点和定期备份来兜底而不是靠一条苛刻的自动规则。第三开启AOF之后别忘了同时关注dir这个配置。RDB和AOF文件都存在dir指定的目录里这个目录必须有足够的磁盘空间并且最好挂在独立的硬盘或云盘上——如果和数据盘挤在一起一旦磁盘满持久化和业务写入一起完蛋。3.2 配置模板直接套用的两套方案如果是新环境我一般按下面的模板来做基准配置。方案A是混合持久化的标准配置适合大多数业务# 基础目录和文件名 dir /var/lib/redis dbfilename dump.rdb appendfilename appendonly.aof # RDB自动触发规则 save 900 1 save 300 10 save 60 10000 # AOF混合持久化 appendonly yes appendfsync everysec no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb aof-use-rdb-preamble yes # 安全机制 stop-writes-on-bgsave-error yes rdbchecksum yes方案B是纯RDB配置适合对丢失窗口不敏感的纯缓存场景dir /var/lib/redis dbfilename dump.rdb save 900 1 save 300 10 save 60 10000 appendonly no stop-writes-on-bgsave-error yes rdbchecksum yes配置改完后先别急着直接重启生效用redis-cli CONFIG GET save确认读取情况需要热更新的参数可以用CONFIG SET临时调整但如果涉及appendonly开关从no切到yes建议还是走一遍完整重启因为启用AOF需要做一次初始的全量重写过程可能阻塞较长时间。3.3 怎么确认持久化真的在工作配置完不等于配置对了。我每次部署完Redis都会用一套固定的检查序列来验证持久化是否真正生效先用redis-cli INFO persistence查看持久化段的各项指标重点关注rdb_bgsave_in_progress是否为0、rdb_last_bgsave_status是否为ok、aof_last_write_status是否为ok。这几个字段直接告诉最近一次持久化是成功还是失败。其次用redis-cli CONFIG GET save确认自动触发规则没有因为配置文件路径不对而用了内置默认值。更直观的验证方式是实际触发一次BGSAVE然后去dir目录看RDB文件是否生成、大小是否合理。在测试环境可以用redis-cli DEBUG SLEEP暂停一下配合验证上线前的演练比什么都重要。4. 备份策略你的持久化文件不等于备份文件4.1 为什么说“有持久化不代表有备份”大多数数据事故根本轮不到“进程崩溃能不能恢复”这一步而是人为误操作直接删了集群里的key、或者整个实例所在的物理机报废。持久化文件再完美它也存放在同一台机器上——进程挂了文件还在但机器烧了、磁盘坏了、rm -rf误删目录了持久化文件本身也跟着没了。我处理过的最典型的事故是运维同学清理服务器磁盘把Redis数据目录当临时文件删了非常贴近真实事故的现场。所以备份的核心思想只有一个把RDB或AOF文件复制到另一台机器或另一个存储介质上。备份是持久化之上的第二道保险二者不能互相替代。4.2 手动备份的两种主要方式对比方式一用redis-cli --rdb直接远程拉取RDB文件。redis-cli -h 127.0.0.1 -p 6379 --rdb backup.rdb这个命令的本质是向Redis发送SYNC指令Replication ID相关Redis会fork子进程生成一个最新的RDB文件并传给你。它的好处是不需要登录到Redis服务器上操作适合从堡垒机拉取备份缺点是生成RDB的过程耗CPU和内存高频执行同样有fork开销。方式二先在服务器本地执行BGSAVE触发快照再把生成的dump.rdb复制走。redis-cli BGSAVE cp /var/lib/redis/dump.rdb /backup/redis-$(date %F).rdb这种方式我能控制备份文件的一致性因为BGSAVE是完整快照不会出现AOF那种“复制到一半还在追加写入”的中间态。复制时注意先执行完BGSAVE再复制或者用cp复制后对比文件大小和md5确保不是拷贝了一半的文件。两种方式我偏向于方案二多一点因为--rdb远程拉取需要Redis开启PSYNC相关支持某些云数据库版本或受限环境下可能不可用。4.3 自动化备份脚本直接抄作业的版本写自动化备份脚本需要关注的维度有四个备份频率、保留周期、远程传输、失败告警。下面这个脚本是我在多个生产环境用过的骨架放在crontab里每天凌晨执行一次可以据此修改#!/bin/bash # redis_backup.sh set -euo pipefail REDIS_HOST127.0.0.1 REDIS_PORT6379 REDIS_AUTHyourpassword DATA_DIR/var/lib/redis BACKUP_DIR/backup/redis KEEP_DAYS7 DATE$(date %Y%m%d_%H%M%S) mkdir -p ${BACKUP_DIR} # 触发BGSAVE并等待完成 redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT} -a ${REDIS_AUTH} EOF 2/dev/null | grep -E OK|Background saving started BGSAVE EOF # 等待RDB生成结束 while true; do STATUS$(redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT} -a ${REDIS_AUTH} INFO persistence 2/dev/null | grep rdb_bgsave_in_progress | cut -d: -f2 | tr -d \r) if [ ${STATUS} 0 ]; then break fi sleep 1 done # 复制并压缩备份 cp ${DATA_DIR}/dump.rdb ${BACKUP_DIR}/redis_${DATE}.rdb gzip ${BACKUP_DIR}/redis_${DATE}.rdb # 清理过期备份 find ${BACKUP_DIR} -name redis_*.rdb.gz -type f -mtime ${KEEP_DAYS} -delete echo Backup completed: ${BACKUP_DIR}/redis_${DATE}.rdb.gz脚本里有两个容易被忽视的细节。一是等待BGSAVE完成时不能只看rdb_bgsave_in_progress还要看rdb_last_bgsave_status是不是ok——如果之前已经失败过一次in_progress为0但文件是坏的脚本照样复制走一个损坏的备份。二是在crontab里执行时PATH环境变量往往不全建议脚本开头写上完整的export PATH/usr/local/bin:/usr/bin:/bin避免redis-cli找不到。备份文件生成后如果条件允许我更建议用rsync或云存储工具把备份再同步一份到异地。比如脚本结尾加一行rsync -avz /backup/redis/ backup-server:/backup/redis/跨机房或者跨地域的备份才有真正的容灾意义。注意别把同步目标的机器和Redis实例放在同一个可用区否则地震断电这类故障一来两边的备份一起没。4.4 备份文件的安全管理很多人备份做完就扔在那里不管等真要用的时候才发现文件早已损坏或过期。关于备份文件管理我有三条建议备份文件一定要带日期和实例标识并记录对应的Redis版本号和当时使用的持久化模式。RDB文件格式在不同大版本之间有时候不兼容比如Redis 6生成的RDB文件在Redis 4上可能加载不了。还原前先确认版本匹配这是最常被忽略的问题之一。定期做“备份可用性测试”比备份本身更重要。我见过不少团队每天自动备份但从没真正还原过一次直到灾难发生才发现备份文件是坏的或者还原流程不对这才是真正的数据安全黑洞。5. 还原实操怎么把数据安全地弄回来5.1 还原前的必做检查清单还原操作往往发生在系统已经出问题的时刻越紧张越容易犯低级错误。我在故障演练中反复强调还原前先把下面五件事确认到位确认文件完整性和校验值。RDB文件的加载依赖文件末尾的CRC64校验如果文件在传输过程中被截断Redis启动时报错的是“Short read or OOM loading DB”。所以拷贝来的备份文件最好先和源文件的md5对一遍同时用redis-check-rdb工具校验一下。确认Redis版本与备份时的版本一致。RDB文件整体上向后兼容做得比较好但AOF文件重放时如果碰到未知命令会直接中断加载过程。确认目标实例上没有需要保留的新数据。还原操作是全量覆盖如果目标实例还在运行且有写入直接替换文件重启会导致这些新写入全部丢失操作之前必须先确认实例可以停机或已经做好切换。确认磁盘空间充足。RDB文件加载到内存需要消耗实际内存文件体积和内存占用不是严格相等的关系特别是Redis内部有大量小对象时加载后的内存占用可能高于文件体积。确认关闭了其他干扰项。如果实例配置了主从复制还原过程中从节点可能尝试与主节点同步导致数据被覆盖。建议先把主从关系解除或者干脆用全新的空实例做还原确认数据无误后再挂到架构里。5.2 RDB全量还原的完整流程RDB还原是所有Redis还原中最常见、也最不容易出错的路径尤其是用混合持久化或纯RDB模式的实例。步骤分六步停止Redis服务。如果实例还在运行且你没打算保留它的内存数据用redis-cli SHUTDOWN NOSAVE强制退出避免退出时自动保存覆盖掉你正要使用的备份文件。清理或移走现有的持久化文件。把dir目录下的dump.rdb和appendonly.aof先移走或改名注意不要直接删除万一没问题还要还原现场。放入备份的RDB文件。确认文件名与配置文件里的dbfilename一致通常就是dump.rdb。如果改过文件名要么把文件改回来要么改配置文件。启动Redis。使用redis-cli查询INFO persistence字段中的loading状态或直接看日志。Redis启动时会自动加载RDB文件日志里会有“DB loaded from disk”的提示。验证数据。用DBSIZE看key总数是否和备份时一致再用SCAN抽样核对几个重要的业务key的值是否正确。如果是大key列表可以用MEMORY USAGE抽查几个关键key。重新接入业务。确认数据无误后再开启主从复制或接回业务流量。5.3 AOF文件与混合模式的还原纯AOF模式下启动时Redis会逐个重放AOF文件里的写命令这个过程比加载RDB慢得多。如果你的AOF文件几十个GB启动可能要花几个小时期间Redis对外不可用这也是我不推荐纯AOF当唯一持久化方案的原因之一。如果AOF文件损坏Redis会拒绝启动并报错要求手动修复。修复工具是redis-check-aofredis-check-aof --fix appendonly.aof修复逻辑很简单也很暴力把文件里损坏的那个命令及其之后的所有内容全部截断丢弃。如果损坏位置很靠前截断会丢掉大量最近的数据但如果损坏位置在文件末尾附近损坏的通常只是最后一小段丢掉的数据量可以接受。用--fix之前先备份AOF文件原样因为工具会直接修改原文件万一修复结果不理想你还有机会重新评估。混合持久化的还原逻辑稍有不同启动时先加载AOF文件开头的RDB部分再重放后面的AOF日志。如果在还原过程中发现混合文件损坏可以把文件截断成只保留RDB首部或者反过来把AOF尾部提取出来单独重放这种高级操作在极端事故场景中很有用但也容易把数据搞得更乱——没有十足的把握求稳还是从最近的RDB备份还原。5.4 跨环境迁移用备份做数据搬家除了故障还原备份还有个高频用途把Redis数据从一台机器迁移到另一台机器或者从测试环境同步到生产环境。迁移的本质就是“备份 还原”。迁移时我强烈建议用RDB文件而不是AOF文件。原因很简单RDB是全量快照文件体积小、加载快、内容一致AOF则是命令流重放期间如果业务还在写入新旧数据混在一起很难保证一致性。还有一种做法是建立主从复制再切主但如果你只想一次性迁移且目标实例不想跟源实例长期保持连接RDB备份还原是最干净的方式。跨环境还原时要注意目标实例的配置差异。比如源实例配置了5个库目标实例默认只开放db0还原后部分key会“消失”实则是它们落在db1到db4里但没查询到。排查这类问题很费劲建议迁移前后都把INFO keyspace打出来逐库对比。6. 真实故障排查那些年我踩过的持久化坑6.1 场景一Redis重启后数据全没了有朋友在群里问过Redis明明配置了save规则也看到过dump.rdb文件为什么重启之后数据还是空。排查后发现实例的启动方式不一样应用是直接启动redis-server不带配置文件的所以持久化相关参数走了内置默认值而他们平时看到的dump.rdb其实是手动执行BGSAVE后生成的重启时配置文件里指定的dir路径和dump.rdb所在路径不一致Redis根本没找到。这个问题的本质是Redis加载RDB文件依赖的是配置文件里的dir和dbfilename两个参数拼接出的完整路径而不是搜遍整个磁盘去找dump.rdb。你用/var/lib/redis/dump.rdb看到了文件但配置文件里写的是/data/redis/dbfilename dump.rdb启动时自然加载不到。排查这类问题先确认redis-cli CONFIG GET dir返回的是什么路径这个路径下有没有dump.rdb。或者直接用strings dump.rdb | head -n 10看文件内容是否包含你预期的key来确认文件本身就是目标数据。6.2 场景二AOF文件损坏启动不了一个线上实例在执行AOF重写时突然断电重启后Redis报错“Bad file format reading the append only file”。这个场景极大概率是重写期间写入的AOF文件不完整或者文件末尾的标记缺失。处理流程先备份损坏文件cp appendonly.aof appendonly.aof.bak redis-check-aof --fix appendonly.aof修复完成后启动实例检查最后几条命令的时间戳确认丢数据量在可接受范围内。如果丢的正好是最近五分钟的高频写入业务侧需要做数据补偿。这里有个经验AOF文件损坏几乎都发生在文件末尾因为每次fsync刷盘都是在尾部追加。--fix截断掉末尾坏块之后绝大多数情况都能正常启动。6.3 场景三BGSAVE总是失败磁盘满还是权限问题见到的次数最多的是磁盘满了。INFO persistence里rdb_last_bgsave_status直接显示err再用df -h一看数据磁盘100%使用率。解决路径是先清理历史log、旧备份、临时文件别急着重启Redis。如果你配置了stop-writes-on-bgsave-error yes此时写入已经被Redis拒绝业务上会有告警优先恢复磁盘空间。还有一种是权限问题比如Redis以redis用户运行但数据目录的所有者是rootBGSAVE创建文件时直接Permission denied。排查时看Redis日志就会露出真实原因不必去猜。6.4 常见问题速查表现象可能原因排查命令/手段重启后数据丢失配置的dir路径不对、没加载到正确的RDB/AOF文件CONFIG GET dir核对文件路径BGSAVE失败磁盘满、权限不足、fork内存不足INFO persistencedf -hdmesg看OOM日志启动报AOF损坏异常断电、AOF重写中断备份后用redis-check-aof --fixRDB加载报checksum错误文件传输截断、磁盘坏道、版本不兼容md5sum对比redis-check-rdb校验数据恢复接近成功但部分key缺失磁盘空间不足、文件被截断、多库问题INFO keyspace逐库比对AOF重写后文件不降反升重写阈值配置不合理日志写量大于压缩收益检查auto-aof-rewrite-percentage和min-size6.5 主从架构下的持久化注意事项用了主从复制之后很多人会想“主节点挂了直接从节点顶上就行不用关心持久化了吧”。这句话大错特错。从节点的数据本质上也是内存副本如果主从全部同时宕机比如机房级故障没有持久化文件的集群照样全丢。正确的姿势是主节点开AOF配合混合持久化从节点至少开RDB用于灾难恢复和重新初始化。有些团队为了节省写IO把从节点的持久化全部关闭一旦集群整体故障恢复就只能靠外部备份。还有一个经常踩的坑主节点AOF重写会fork子进程消耗CPU这个操作如果发生在业务高峰可能让主节点请求毛刺变多。解决办法是把auto-aof-rewrite-percentage调大一点或者安排低峰期手动执行BGREWRITEAOF把重写时机控制在自己手里。7. 结尾一点经验之谈持久化、备份、还原这些事平时没人注意一到出故障才发现文档没写、脚本没有、流程不知道那种手忙脚乱的滋味我体会过太多次了。如果你只记住四件事我觉得就算这篇没白看第一持久化优先选混合方案难以取舍就别跟风纯RDB的“轻量”人设第二备份必须异地且定期做还原演练文件和流程都不演练等于没做第三修改持久化配置前一定确认相关路径和版本别让“文件明明在却加载不到”这种低级问题折腾一宿第四把排查命令和备份脚本放到一个随时能翻到的文档里真出事的时候能少死不少脑细胞。我在实际运维中的一个习惯是每次给Redis做架构调整都会顺手验证一次“删掉一个key - 从昨晚备份还原 - 数据恢复”五分钟的演练能换来后续若干个安稳的睡眠。这个习惯推荐给每一个对数据负责的人。
返回列表