1. 从一次数据丢失事故说起:Redis持久化到底在解决什么问题
几年前我负责过一个实时竞价系统的缓存层,某天凌晨机房电力闪断,UPS切换的几十秒间隙里,一批正在内存中待落库的竞价结果直接蒸发。事后复盘发现,Redis实例只开了RDB,而且save触发间隔设得过于稀疏,最后一次快照停留在事故前七分钟。七分钟对多数业务也许只是几条日志,对竞价系统却是真金白银的损失。那次之后我把Redis持久化翻了个底朝天,也踩遍了RDB和AOF的各种坑。
这篇文章就围绕中间件Redis持久化方式展开,把RDB、AOF、混合持久化这三条路讲透。你如果是刚接触Redis的开发者,看完能明白该选哪种、参数怎么配;如果你已经在生产环境跑着Redis,这里面的排查思路和避坑经验应该能帮你少走弯路。核心关键词就三个:RDB快照、AOF日志、混合持久化,全文围绕它们展开,不跑偏。
先说清楚一个前提:Redis是内存数据库,数据默认只活在内存里,进程一挂、机器一断电,内存清空,数据就没了。持久化的本质,就是把内存里的数据以某种形式写到磁盘上,重启时再读回来。听起来简单,但“什么时候写、写什么格式、写多频繁”这三个问题,直接决定了你丢数据的概率和Redis的性能表现。RDB、AOF、混合持久化,就是这三问的三种不同答案。
2. 三种持久化方式的整体设计与选型逻辑
2.1 RDB:定期给内存拍一张“全家福”
RDB的全称是Redis Database,思路非常直白:在某个时间点,把内存里所有键值对做一次全量序列化,写成一个二进制文件,默认叫dump.rdb。你可以把它理解成给整个数据库拍一张照片,照片里记录了那一刻所有数据的样子。
这种方式的优点很突出。二进制文件体积小,一个几GB内存的实例,RDB文件可能只有几百MB;恢复速度快,因为读的是紧凑的二进制格式,不需要逐条重放命令;对主进程性能影响可控,因为Redis会fork一个子进程去写盘,主进程继续处理请求。缺点同样明显:两次快照之间的数据是裸奔的,一旦宕机,这段时间的写入全部丢失。而且fork子进程时如果内存很大,会有明显的卡顿,这个后面细说。
2.2 AOF:把每条写命令记成“流水账”
AOF全称Append Only File,思路是另一个极端:不拍照片,而是把每一条会修改数据的写命令,按顺序追加到一个日志文件里。重启时,Redis把这个日志从头到尾重放一遍,数据就回来了。这就像记账,每一笔支出都记下来,月底一汇总就知道钱花哪了。
AOF的好处是丢数据少。你可以配置成每秒同步一次(everysec),最坏情况丢一秒的数据;甚至配置成每条命令都同步(always),理论上不丢数据,但性能会大打折扣。AOF的代价是文件体积大,因为记的是命令而不是数据本身,同一条key被改了一百次就记一百条;恢复速度也慢,因为要逐条重放。另外AOF文件会随着时间不断膨胀,需要定期重写(rewrite)来压缩。
2.3 混合持久化:照片加流水账的组合拳
Redis 4.0之后引入了混合持久化,思路是把两者的优点捏在一起。开启后,AOF重写时不再单纯记命令,而是先把当前内存数据以RDB格式写进AOF文件的开头,后面再追加增量命令。重启时先加载RDB部分快速恢复大部分数据,再重放后面的增量命令补齐。
这个设计很聪明:RDB部分保证了恢复速度,增量AOF部分保证了数据完整性。它本质上是AOF的一种增强模式,文件还是那个AOF文件,只是内容结构变了。对于既想要快恢复、又想要低丢失的场景,混合持久化基本是当前的最优解。
2.4 选型决策:没有最好,只有最合适
到底选哪种,取决于你能容忍丢多少数据、能接受多大性能损耗、恢复速度要求多高。我整理了一张对照表,把关键维度摆出来:
| 维度 | RDB | AOF | 混合持久化 |
|---|---|---|---|
| 数据丢失风险 | 高(取决于快照间隔) | 低(最坏丢1秒) | 低(同AOF) |
| 文件体积 | 小 | 大 | 中等 |
| 恢复速度 | 快 | 慢 | 快 |
| 对性能影响 | fork时卡顿 | 持续写盘开销 | fork时卡顿 |
| 适用场景 | 缓存、可容忍丢数据 | 数据敏感、不能丢 | 兼顾两者 |
我的经验是:纯缓存场景,数据丢了能从数据库重建,用RDB就够了,省资源;订单、账户这类不能丢的数据,上AOF加everysec,或者直接混合持久化;如果内存特别大(比如几十GB),fork开销要重点评估,可能得调大实例数、减小单实例内存。
3. RDB核心细节与实操配置要点
3.1 触发机制:自动、手动、还有你不知道的隐式触发
RDB的触发分三类。第一类是自动触发,靠配置文件里的save规则,比如save 900 1表示900秒内至少1个key变化就触发,save 300 10、save 60 10000是常见的组合。第二类是手动触发,SAVE命令会阻塞主进程直到写完,生产环境基本不用;BGSAVE会fork子进程后台写,是手动触发的正确姿势。第三类是隐式触发,比如主从复制时从节点首次同步、执行SHUTDOWN时如果配了save规则、以及FLUSHALL之后,都可能触发RDB。
这里有个容易忽略的点:save规则是“或”的关系,任意一条满足就触发。很多人配了三条规则以为是叠加,其实不是。另外如果同时开了AOF,Redis启动时会优先用AOF恢复,因为AOF数据更全,RDB这时候只是备胎。
3.2 fork与写时复制:RDB性能问题的根源
RDB最大的性能隐患在fork。Redis是单线程处理命令的,fork子进程这个动作本身需要复制父进程的页表,内存越大,页表越大,复制耗时越长,主进程就被阻塞得越久。我实测过一个16GB的实例,fork一次主进程卡了将近200毫秒,对高并发服务来说这200毫秒足以引发超时雪崩。
fork之后靠的是写时复制(Copy On Write)。子进程和父进程共享内存页,只有当父进程要修改某个页时,才复制一份出来。这意味着如果快照期间写入很频繁,内存会膨胀,极端情况下可能翻倍。所以RDB快照期间要盯着内存和延迟,别让写入把内存撑爆。
提示:如果实例内存超过10GB,建议把
save规则调稀疏,或者干脆关掉自动RDB,改用从节点做持久化,主节点专心扛流量。
3.3 关键参数与实操配置
RDB相关参数不多,但每个都值得推敲:
save <seconds> <changes>:触发规则,可以配多条,也可以配空字符串save ""来关闭自动RDB。dbfilename dump.rdb:文件名,改个有意义的名字方便运维识别。dir ./:文件存放目录,生产环境一定要指向独立磁盘,别和AOF、日志挤在一起。rdbcompression yes:是否压缩,默认开,省磁盘但费一点CPU,通常值得开。rdbchecksum yes:是否校验和,默认开,能发现文件损坏,建议保留。stop-writes-on-bgsave-error yes:快照失败时是否停止写入,默认开。这个参数争议大,开了能防止数据继续丢,但可能让服务不可写;关了服务可用但数据风险高。我一般保持开启,配合监控告警。
配置示例:
# 900秒内1个key变化,或300秒内10个,或60秒内10000个 save 900 1 save 300 10 save 60 10000 dbfilename redis-6379.rdb dir /data/redis/rdb rdbcompression yes rdbchecksum yes stop-writes-on-bgsave-error yes3.4 RDB实操心得与避坑
第一,别在业务高峰期手动BGSAVE。fork的卡顿叠加高峰流量,很容易触发连锁反应。要手动备份,挑凌晨低峰期。
第二,监控rdb_last_bgsave_status和rdb_last_bgsave_time_sec这两个指标。前者告诉你上次快照成没成,后者告诉你耗时多久。耗时突然变长,往往是内存涨了或者磁盘慢了。
第三,RDB文件不是备份的终点。它只是某一刻的快照,要配合定时任务把文件同步到异地,否则磁盘坏了照样全丢。我见过只留本地RDB、结果磁盘故障数据全没的案例。
4. AOF核心细节与实操配置要点
4.1 写入流程:命令先到缓冲区,再落盘
AOF的写入不是每条命令直接写磁盘,而是分三步:命令先追加到aof_buf缓冲区,然后根据appendfsync策略决定何时调用系统调用写到磁盘,最后操作系统再决定何时真正刷到物理磁盘。这个链条里任何一环出问题,都可能丢数据。
appendfsync有三个取值,直接决定丢数据窗口:
always:每条命令都同步刷盘,最安全,但性能最差,QPS可能掉到几千。everysec:每秒同步一次,最坏丢一秒数据,性能和安全平衡,生产环境首选。no:交给操作系统决定,通常30秒左右刷一次,丢数据风险大,不推荐。
4.2 AOF重写:给流水账“做减法”
AOF文件会越来越大,因为同一个key的多次修改都被记下来了。比如一个计数器自增了一万次,AOF里就有一万条INCR命令,但其实最终值只需要一条SET就能表达。AOF重写就是干这个的:读当前内存数据,用最少的命令重新生成一个AOF文件。
重写分自动和手动。自动靠auto-aof-rewrite-percentage和auto-aof-rewrite-min-size两个参数,比如前者设100表示文件比上次重写后大一倍就触发,后者设64mb表示文件至少64MB才触发。手动用BGREWRITEAOF命令。重写同样是fork子进程,同样有写时复制的开销,这点和RDB一样。
4.3 关键参数与实操配置
AOF参数比RDB多,也更容易配错:
appendonly yes:总开关,默认no,要手动开。appendfilename "appendonly.aof":文件名。appendfsync everysec:同步策略,生产推荐everysec。no-appendfsync-on-rewrite no:重写期间是否暂停同步。设yes能减少阻塞但可能丢更多数据,设no更安全但重写时延迟会抖。我一般设no。auto-aof-rewrite-percentage 100:增长比例阈值。auto-aof-rewrite-min-size 64mb:最小重写体积。aof-load-truncated yes:AOF文件末尾损坏时是否继续加载。设yes能启动但可能丢最后一点数据,设no直接启动失败。生产建议yes配合告警。aof-use-rdb-preamble yes:是否开启混合持久化,4.0之后默认yes。
配置示例:
appendonly yes appendfilename "appendonly-6379.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 yes4.4 AOF实操心得与避坑
第一,everysec不是绝对安全。如果磁盘IO打满,后台同步线程可能被阻塞,实际同步间隔会超过一秒。所以磁盘性能要留余量,别让AOF和别的重IO服务抢盘。
第二,AOF重写期间如果写入量巨大,内存可能膨胀。因为写时复制要保留旧页,新写入又占新页。重写期间要盯内存,必要时限流。
第三,AOF文件损坏是运维噩梦。redis-check-aof工具能修复,但会截断损坏部分。所以AOF文件也要定期备份,别等坏了才后悔。
第四,aof-load-truncated yes虽然能让服务起来,但意味着你丢了数据。要配合监控,一旦发现截断加载,立刻排查磁盘和写入链路。
5. 混合持久化与完整实操流程
5.1 混合持久化的工作原理
混合持久化是AOF的一个模式,由aof-use-rdb-preamble控制。开启后,AOF重写时生成的新文件结构变成:开头是一段RDB格式的全量数据,后面是重写期间的增量AOF命令。重启加载时,Redis先识别开头的RDB魔数,快速加载全量部分,再重放后面的增量命令。
这个设计的好处是恢复速度接近RDB,数据完整性接近AOF。代价是文件格式变复杂,老版本Redis(4.0之前)读不了,降级时要先转成纯AOF。另外RDB部分和AOF部分的分界点要处理好,否则加载会出错。
5.2 从零搭建一套持久化配置
假设你有一台新机器,要部署一个数据敏感的Redis实例,我按顺序走一遍:
第一步,规划目录。RDB、AOF、日志分开放,避免互相影响:
mkdir -p /data/redis/6379/{rdb,aof,logs}第二步,写配置文件redis-6379.conf:
port 6379 dir /data/redis/6379/rdb dbfilename dump-6379.rdb save 900 1 save 300 10 save 60 10000 rdbcompression yes rdbchecksum yes stop-writes-on-bgsave-error yes appendonly yes appendfilename "appendonly-6379.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 yes logfile /data/redis/6379/logs/redis.log第三步,启动并验证:
redis-server /data/redis/6379/redis-6379.conf --daemonize yes redis-cli -p 6379 info persistence看aof_enabled:1、rdb_last_bgsave_status:ok、aof_last_bgrewrite_status:ok这几个字段,确认都正常。
第四步,压测验证。用redis-benchmark打一轮写入,同时观察info persistence里的rdb_last_bgsave_time_sec和aof_rewrite_time_sec,确认延迟在可接受范围。
5.3 恢复演练:别等真出事才第一次恢复
持久化配好了不代表能恢复。我强烈建议每季度做一次恢复演练:把RDB和AOF文件拷到测试机,启动Redis,比对数据条数和抽样key的值。演练能暴露很多问题,比如文件权限不对、目录不存在、版本不兼容。
恢复时的优先级要清楚:如果AOF开着且文件完整,Redis优先用AOF;AOF关了或文件损坏且aof-load-truncated no,才回退到RDB。所以AOF和RDB同时开时,RDB其实是第二道防线,别以为配了RDB就万事大吉。
5.4 参数计算:save规则怎么定才合理
save规则不是拍脑袋定的,要结合业务能容忍的丢失窗口和写入频率。假设你的业务每秒写入1000条,能容忍最多丢5分钟数据,那save 300 1就够了,因为300秒内肯定有写入。但如果写入很稀疏,比如每分钟才几条,那save 300 1可能300秒内没触发,得配save 60 1这种更激进的规则。
再算AOF重写阈值。假设AOF每秒增长1MB,你希望文件不超过1GB,那auto-aof-rewrite-min-size设512mb、auto-aof-rewrite-percentage设100,文件到512MB后翻倍到1GB就触发重写,基本能控制在1GB附近。
6. 常见问题与排查技巧实录
6.1 快照失败:磁盘满、权限错、fork被拒
rdb_last_bgsave_status:err是最常见的告警。排查顺序:先看磁盘空间df -h,再看目录权限ls -ld,最后看系统日志有没有fork失败。fork失败通常是内存不够或进程数超限,vm.overcommit_memory设1能缓解,ulimit -u要调大。
6.2 AOF文件暴涨:重写没触发或触发太晚
AOF文件突然涨到几十GB,多半是重写没生效。检查auto-aof-rewrite-percentage是不是设太大,或者auto-aof-rewrite-min-size设太高导致一直不触发。也可能是重写一直失败,看aof_last_bgrewrite_status。临时救急可以手动BGREWRITEAOF。
6.3 恢复慢:AOF太大或混合持久化没开
重启时加载几十分钟,通常是AOF文件太大且没开混合持久化。解决办法是开aof-use-rdb-preamble yes,然后手动触发一次重写,让文件变成RDB加增量的结构。下次重启就快了。
6.4 主从切换后数据不一致
主从复制时,从节点也会做持久化。如果主节点RDB和从节点AOF策略不一致,切换后可能丢数据。建议主从用同一套持久化配置,或者从节点只做RDB、主节点做AOF,切换时心里有数。
6.5 常见问题速查表
| 现象 | 可能原因 | 排查动作 | 解决方向 |
|---|---|---|---|
| 快照失败 | 磁盘满/权限错/fork失败 | df、ls、系统日志 | 清盘、改权限、调overcommit |
| AOF暴涨 | 重写未触发/失败 | info persistence | 调阈值、手动重写 |
| 恢复慢 | AOF大/无混合持久化 | 看文件大小和格式 | 开混合、触发重写 |
| 数据丢失 | 同步策略太松 | 看appendfsync | 改everysec或always |
| 内存膨胀 | 重写期间写入多 | 监控内存曲线 | 限流、错峰重写 |
6.6 独家避坑技巧
第一,stop-writes-on-bgsave-error别轻易关。关了服务可用,但数据一直在丢,等发现时可能已经丢了几小时。开着虽然服务不可写,但至少问题暴露得早。
第二,AOF和RDB别放同一块盘。一块盘坏了两个都完,分盘放至少能保一个。
第三,定期用redis-check-rdb和redis-check-aof检查文件完整性,别等恢复时才发现文件坏了。
第四,监控要覆盖rdb_last_bgsave_time_sec、aof_rewrite_time_sec、aof_current_size、rdb_changes_since_last_save这几个指标,任何一个异常都可能是事故前兆。
第五,大内存实例考虑用从节点做持久化,主节点关掉RDB和AOF,专心扛流量,从节点慢慢写盘。这样主节点没有fork卡顿,从节点延迟高一点但能接受。
7. 我个人的一点经验体会
折腾Redis持久化这些年,最大的感受是:没有一劳永逸的配置,只有持续监控和定期演练。RDB、AOF、混合持久化各有适用场景,选型时先问自己两个问题——能丢多少数据、能接受多大性能损耗。答案清楚了,配置自然就定了。
另外别迷信默认配置。Redis默认不开AOF,默认save规则对高写入场景偏稀疏,这些默认值是为通用场景设计的,你的业务未必通用。花半小时读一遍配置文件,比出事后熬夜排查划算得多。最后再分享一个小技巧:把info persistence的输出接入监控,设几个阈值告警,能挡掉八成持久化相关的线上问题。