REJAVA / LEARNING NOTES
Redis(内存数据存储)持久化:RDB 与 AOF
RDB 与 AOF 各记什么、断电丢多少,两者都开时怎样选和配置。
根据课程与真实学习交流整理;不是逐字稿
本次更新说明
- 整理为公开学习笔记:fork 快照、三档刷盘、7.0 多文件 AOF 重写、开启 AOF 的步骤,含 3 张图示。
- 附课后问答:区分进程崩溃、正常关闭与断电,以及刷盘策略的真实损失范围。
热身 · 一 问题从哪来 · 二 RDB(快照文件)快照 · 三 AOF(追加写日志)追加日志 · 四 丢多少看刷盘 · 五 AOF(追加写日志)越写越大 · 六 两个都开 · 七 怎么选 · 八 应用 · 检验 · 表达 · 追问 · 隔日复测
本课要解决什么问题
学完本课,给你一个Redis(内存数据存储)实例的持久化配置(开了RDB(快照文件)还是AOF(追加写日志)、save规则是什么、appendfsync(追加日志刷盘策略)是哪一档)和一个故障时点(断电、进程被杀、正常重启),你能说出重启后最多丢多少数据、为什么;能解释RDB(快照文件)快照是怎样在不停服的情况下生成的、AOF(追加写日志)为什么会越写越大、重写解决了什么;还能根据“业务能容忍丢多少”给出配置建议。
第16课结尾留了一个追问:“Redis(内存数据存储)的数据都在内存里,重启后会全部丢失吗?”这一课就回答它。常见的问法是:“Redis(内存数据存储)持久化有哪几种?RDB(快照文件)和AOF(追加写日志)的区别?AOF(追加写日志)的fsync(强制同步到磁盘)策略怎么选?”这也是第14课MySQL(关系型数据库)日志那套思路的又一次应用:先分清“写进内存、交给操作系统、可靠落盘”三个阶段,再判断丢多少。
版本: 以Redis(内存数据存储) Open Source 8.10.2的配置文件为准;7.0起AOF(追加写日志)改为多文件结构,7.0以前的差异会单独标出。官方文档里没有“混合持久化”这个词,它说的是“AOF(追加写日志)的基础文件用RDB(快照文件)格式”,本课按官方说法讲,并注明常见资料里的叫法。
前置: 第14课“写了日志”的三个阶段;第16课的数据类型。 必学: 第一至八节、六道选择题和四问表达。第四节后可暂停。fork(创建子进程)的代价、损坏文件的修复放在折叠拓展里。本课没有实际运行Redis(内存数据存储)做断电实验,所有“丢多少”都是官方文档的说法。
主读: 官方 Redis(内存数据存储) persistence(持久化)。先读开头的四种选项和“RDB(快照文件) advantages/disadvantages”“AOF(追加写日志) advantages/disadvantages”,再读“Ok, so what should I use?”;“Log rewriting”和“Interactions between AOF(追加写日志) and RDB(快照文件) persistence”配合本课第五、六节读。本课引用的官方页面都列在文末“官方来源”。
热身:先把第16课的三个要点说一遍
先凭记忆回答,再展开核对。答不上来就记一句,接着往下学,这不是进入新课的门槛。
第16课:一个线程怎么照看成百上千个连接?某个连接的数据还没到时,Redis(内存数据存储)是等它,还是做别的?
做别的。线程同时盯着所有连接,哪个连接的数据到了就处理哪个,数据没到的先不管,这叫I/O多路复用。所以它不会卡在等网络上。对照第16课课后问答。
第16课:过期的键是怎么被删除的?覆盖值时想保留原来的过期时间,SET要加什么选项?
两种方式:访问时发现已过期就删除;定期从带过期时间的键里随机抽查一部分,删掉已过期的。覆盖值时保留过期时间用 KEEPTTL(6.0起)。
第16课:SCAN和SSCAN各遍历什么?怎样判断遍历结束?
SCAN遍历整个库的键,SSCAN遍历一个集合内部的成员。返回的游标为0才算结束,某一批返回空不代表结束;同一个元素可能返回多次。
第14课:“日志写了”可能指哪三个阶段?只有到哪一个阶段,断电才不丢?
①还在进程内存里;②已交给操作系统写文件,可能还在操作系统缓存里;③已可靠同步到磁盘。只有第③阶段断电不丢。本课AOF(追加写日志)的三档刷盘策略,区别就在命令多久从第②阶段推进到第③阶段。
一、问题从哪来:缓存丢了没关系,计数丢了有关系
“Redis(内存数据存储)数据在内存里,重启就没了吗?需要持久化吗?”
回到第16课的办件系统。Redis(内存数据存储)里放着两类东西:
- 办件详情缓存
case:detail:20:丢了没关系,下次从MySQL(关系型数据库)再查一遍就有。 - 每日受理量
stat:accept:2026-10-09、 登录失败计数login:fail:账号:这些只在Redis(内存数据存储)里,MySQL里没有。Redis(内存数据存储)一重启,当天的受理量就归零,刚被锁定的账号也解锁了。
所以“要不要持久化”先看里面放了什么。官方把选项列成四种: RDB(快照文件) ,按设定的间隔给整个数据集做时间点快照; AOF(追加写日志) ,记录服务器收到的每一个写操作,重启时重放一遍重建数据; 不持久化 ,纯缓存场景有时这样用; RDB(快照文件)+AOF(追加写日志) ,两个一起开。[1] 下面两节分别看前两种各记什么、怎么生成。
先试着自己解释,再看参考回答
要不要持久化,看Redis(内存数据存储)里放的是什么。只是数据库的缓存,重启后重新加载就行,可以不持久化;如果有只存在Redis(内存数据存储)里的数据,比如计数、限流窗口,就需要持久化。官方给四种选项:RDB(快照文件)定期做整体快照,AOF(追加写日志)记录每个写命令、重启时重放,不持久化,或者两个都开。
二、RDB(快照文件):某一刻整个数据集的照片
“RDB(快照文件)是什么?做快照的时候Redis(内存数据存储)会停下来吗?”
RDB(快照文件)是某一时刻整个数据集的二进制快照,默认写在 dump.rdb 文件里。什么时候拍这张“照片”,由配置文件里的 save 规则决定:save 秒数 变化次数 表示“过了这么多秒,并且期间至少有这么多次写操作”就保存一次。官方默认三档:
| 规则 | 意思 |
|---|---|
save 3600 1 | 距上次保存满1小时,且期间至少改过1次 |
save 300 100 | 距上次保存满5分钟,且期间至少改过100次 |
save 60 10000 | 距上次保存满1分钟,且期间至少改过10000次 |
三档同时生效,满足任何一条就保存。两个条件要同时满足:10:00保存过一次,之后4分30秒里改了270次,不会触发 save 300 100,因为还没满5分钟。写 save "" 可以完全关闭快照。也可以手动执行 BGSAVE(后台保存)触发一次。[2]
拍照时Redis(内存数据存储)不停服:调用fork(创建子进程)去写
办件系统白天一直有人在改数据,Redis(内存数据存储)怎么在不停止服务的情况下,把“某一刻”的完整数据写进文件?官方说明的过程是:Redis(内存数据存储)调用 fork(创建子进程),于是有了父、子两个进程;子进程把数据集写进一个临时RDB(快照文件)文件;写完后用新文件替换旧文件。父进程继续服务客户端,从头到尾不做磁盘I/O。[2]
这也解释了RDB(快照文件)的优缺点。 好处: 文件紧凑、一个文件就是完整数据,适合备份;父进程只负责fork(创建子进程),性能影响小;数据量大时,用RDB(快照文件)重启比用AOF(追加写日志)快。 代价: 快照通常几分钟才做一次,如果Redis(内存数据存储)没有正常关闭就停了(比如断电),官方的说法是“要做好丢失最近几分钟数据的准备”。[3]
回到办件例子:上午10:00做了一次快照,10:04断电。10:00到10:04之间的受理量、登录失败计数全部丢失,重启后回到10:00的状态。
拓展:fork(创建子进程)本身要花多久?
fork(创建子进程)在主线程执行,本身就会造成一次停顿。官方解释主要开销是复制页表:Linux上内存按4 KB分页,每页在页表里占8字节,一个24 GB的Redis(内存数据存储)实例,页表就有48 MB要复制。官方给的实测:物理机上6.9 GB的实例fork(创建子进程)用了62毫秒(约9毫秒/GB);老式Xen虚拟机上6.1 GB用了1460毫秒(约239毫秒/GB)。所以数据集很大、CPU(处理器)又不好时,官方提醒fork(创建子进程)可能让Redis(内存数据存储)停服几毫秒甚至一秒。[4] [3]
先试着自己解释,再看参考回答
RDB(快照文件)是某一时刻整个数据集的二进制快照,默认文件是dump.rdb,按save规则定期生成,比如5分钟内改了100次就保存一次,也可以BGSAVE(后台保存)手动触发。生成时Redis(内存数据存储) fork(创建子进程)出一个子进程,子进程把fork(创建子进程)那一刻的数据写进临时文件再替换旧文件,父进程继续服务,利用写时复制只复制被改动的内存页,所以不停服。优点是文件紧凑、适合备份、重启快;缺点是两次快照之间的数据在断电时会丢,官方说要做好丢最近几分钟的准备;fork(创建子进程)本身在大实例上也有几十毫秒到一秒的停顿。
三、AOF(追加写日志):把每一条写命令记下来
“AOF(追加写日志)记的是什么?重启时怎么用它恢复?”
官方直接说“快照不够耐久”:机器停了、断电了、或者你不小心 kill -9,最近写入的数据就没了。AOF(追加写日志)是另一种策略:配置文件里写 appendonly yes(默认是 no)之后,Redis(内存数据存储)每收到一条会改变数据的命令(比如SET),就把它追加到AOF(追加写日志)文件末尾;重启时把文件里的命令从头到尾重新执行一遍,数据就回来了。命令按Redis(内存数据存储)协议原样记录,所以文件是能看懂的。[5]
还是办件例子。10:00:01到10:00:05这三条命令,RDB(快照文件)快照里没有,AOF(追加写日志)里有:
SET case:detail:20 {"id":20,...}
INCR stat:accept:2026-10-09
DEL login:fail:a
验证思路:AOF(追加写日志)实际按Redis(内存数据存储)协议格式存储(每条命令前有参数个数和长度标记),这里按命令写出便于阅读。本课没有实际打开AOF(追加写日志)文件核对。
和第14课对照一下:MySQL(关系型数据库)的redo log(重做日志)记的是“页面上哪些记录改成了什么”,AOF(追加写日志)记的是“收到了哪条命令”;但两者思路相同,都是 先把变更追加到日志,重启后照着日志把状态补回来 。AOF(追加写日志)是只追加的日志,官方说因此没有寻道,断电也不会把文件中间写坏;即使最后一条命令只写了一半,也能处理(见第六节拓展)。[6]
Redis(内存数据存储) 7.0起,AOF(追加写日志)不再是一个文件,而是放在 appendonlydir 目录里的一组文件:至多一个 基础文件 (base,代表上次重写时的完整快照,默认用快照文件格式)、若干个 增量文件 (incr,记录基础文件之后的写命令),由一个 清单文件 (manifest)跟踪。例如 appendonly.aof.1.base.rdb、appendonly.aof.1.incr.aof、appendonly.aof.manifest。为什么要拆成这样,第五节讲重写时说明。[5] [7]
先试着自己解释,再看参考回答
AOF(追加写日志)是追加写的日志,开启后Redis(内存数据存储)每执行一条修改数据的命令就把它追加到文件末尾,重启时从头重放这些命令重建数据。它和MySQL的redo思路一样,先记变更、重启后补回状态;区别是AOF(追加写日志)记的是命令本身。因为只追加,没有寻道,断电也不会写坏文件中间。7.0起AOF(追加写日志)拆成一个基础文件加若干增量文件,由清单文件管理,放在appendonlydir目录里。
四、丢多少,看命令写到了哪个阶段:appendfsync(追加日志刷盘策略)三档
“AOF(追加写日志)的fsync(强制同步到磁盘)策略有哪几种?everysec会丢多少?”
“追加到AOF(追加写日志)文件”这句话,和第14课一样要追问:追加到了哪一层?Redis(内存数据存储)先把命令写进自己的缓冲区,再调用 write 交给操作系统,最后要 fsync(强制同步到磁盘)才算真正落盘。appendfsync(追加日志刷盘策略) 决定多久做一次fsync(强制同步到磁盘),官方给了三档:[8]
| appendfsync(追加日志刷盘策略) | 官方说明 | 断电时丢多少 |
|---|---|---|
always | 每次追加后都fsync(强制同步到磁盘)。官方原话“非常非常慢,非常安全”;多个客户端同时写时会合并成一次写、一次fsync(组提交),回复客户端之前完成 | 这次fsync(强制同步到磁盘)在回复客户端之前完成,因此几乎不丢;官方只说“very safe”,没有承诺零丢失 |
everysec(默认) | 每秒fsync(强制同步到磁盘)一次,由后台线程执行;主线程尽量在没有fsync(强制同步到磁盘)进行时写入。官方说速度“足够快”,2.4起和快照差不多 | 最多约1秒的写入 |
no | 从不主动fsync(强制同步到磁盘),交给操作系统。最快、最不安全;Linux默认通常约30秒刷一次 | 可能丢约30秒,取决于内核设置 |
官方依据:[8]。配置文件的说明还区分了两种故障: 断电 时默认策略丢一秒;如果只是 Redis(内存数据存储)进程自己出问题 、操作系统还正常,已经交给操作系统的数据不会丢,只可能丢一条写。这正是第14课“②交给操作系统”和“③可靠同步”的区别。为什么只丢一条:Redis(内存数据存储)在每轮处理完命令、回复客户端之前,就把这一批命令write给操作系统;进程崩溃时还停在①的,最多只有最后一批。[8]
有些资料说everysec“最坏丢2秒”。官方对丢失量的说法是“一秒”;“两秒”出自延迟诊断页,指fsync(强制同步到磁盘)还没做完时,Redis(内存数据存储)会把下一次write推迟最多两秒。被推迟的命令还停在①Redis(内存数据存储)内存里,所以磁盘很慢时,实际丢失可能超过一秒。回答时先说官方的一秒,再补这个边界。[9]
回到办件系统:默认everysec下,10:04:30断电,最多丢10:04:29之后那一秒内的受理计数;10:00到10:04:29的全都在。对比第二节只开RDB(快照文件)时丢掉的四分钟,差别很明显。
先试着自己解释,再看参考回答
追加到AOF(追加写日志)要经过Redis(内存数据存储)缓冲、操作系统缓存、fsync(强制同步到磁盘)到磁盘三个阶段,appendfsync(追加日志刷盘策略)决定多久fsync(强制同步到磁盘)一次。always每条命令写入后都fsync(强制同步到磁盘),最安全但很慢;everysec是默认值,后台线程每秒fsync(强制同步到磁盘)一次,断电最多丢一秒的写入,如果只是Redis(内存数据存储)进程崩溃而系统正常,只丢一条;no交给操作系统,Linux通常约30秒才刷一次,断电可能丢几十秒。一般用默认的everysec,官方说这是速度和安全的合理折中。
这里可以暂停。先能说清“RDB(快照文件)丢几分钟、AOF(追加写日志)默认丢一秒、原因是命令停在哪个阶段”,再继续看AOF(追加写日志)重写和两者配合。
五、AOF(追加写日志)越写越大:重写把它压缩成最短命令序列
“AOF(追加写日志)文件会无限增长吗?重写是怎么回事?”
官方举的例子很直接:对一个计数器INCR了100次,数据集里只有一个键、一个最终值,AOF(追加写日志)里却有100条记录,其中99条对重建当前状态没有用。办件系统的每日受理量一天INCR几万次,AOF(追加写日志)就多几万条记录。[10]
重写 就是让Redis(内存数据存储)根据 当前内存里的数据 ,重新生成一份“重建当前数据集所需的最短命令序列”,用它替换掉旧的、冗长的日志。手动执行 BGREWRITEAOF(后台重写追加日志) 可以触发;配置文件里的 auto-aof-rewrite-percentage 100 和 auto-aof-rewrite-min-size 64mb 表示“文件比上次重写后大了100%、且至少64 MB”时自动触发。[10] [7]
7.0起重写期间的新写入去哪了
重写也是fork(创建子进程)一个子进程来做,和RDB(快照文件)一样不停服。那么重写进行的这几秒钟里,父进程新收到的写命令记到哪里?这正是第三节“基础文件+增量文件”要解决的问题。官方说明7.0起的流程:安排重写时,父进程 打开一个新的增量文件 继续追加;子进程根据当前数据生成一个新的基础文件;两者都就绪后,用一个临时清单文件原子地切换过去。为什么正好接得上:父进程在fork(创建子进程)的同一刻开始写新的增量文件,子进程写的新基础文件正是fork(创建子进程)那一刻的数据,一个记“那一刻之前的全部”、一个记“那一刻之后的每一条”,旧的基础文件和增量文件切换后就可以删除。[10]
7.0以前没有这套结构:重写期间的新命令要先攒在内存缓冲区里,重写完成后再追加到新文件末尾,官方列的缺点是“重写期间可能占大量内存”“这些命令要写两次磁盘”。常见追问“7.0的多文件AOF(追加写日志)解决了什么”,答的就是这两点。[6]
基础文件为什么默认是RDB(快照文件)格式: 配置项 aof-use-rdb-preamble yes 表示重写生成的基础文件用RDB(快照文件)格式而不是命令列表。官方说RDB(快照文件)格式总是更快、更省空间,关掉它只为兼容旧版本。所以默认设置下,重写得到的“最短序列”其实是以RDB(快照文件)格式写进基础文件的,不是一串命令。这个选项4.0.0的配置文件里默认是no,5.0.0起默认就是yes。国内不少资料常把它叫“混合持久化”,官方没有用这个词;回答时可以说“AOF(追加写日志)的基础文件用RDB(快照文件)格式,加载时先读RDB(快照文件)部分再重放后面的命令”。[7]
先试着自己解释,再看参考回答
AOF(追加写日志)记录的是每一条写命令,计数器加100次就有100条,其中99条对恢复没用,所以文件会越来越大。重写是让Redis(内存数据存储)按当前内存数据重新生成最短的命令序列替换旧日志,可以BGREWRITEAOF(后台重写追加日志)手动触发,也可以按增长百分比和最小体积自动触发。重写由子进程完成,7.0起父进程在重写期间直接打开一个新的增量文件继续写,子进程生成新的基础文件,最后用清单文件原子切换;7.0以前要把这段时间的命令缓冲在内存里再追加,占内存还要写两次。基础文件默认用RDB(快照文件)格式,加载更快,常见资料叫混合持久化,官方叫RDB(快照文件) preamble。
六、两个都开:重启时读哪个,备份怎么做
“RDB(快照文件)和AOF(追加写日志)同时开启,重启时用哪个恢复?”
官方的规则只有一条: 两者都开时,重启用AOF(追加写日志)重建数据,因为它保证是最完整的。 配置文件也写着:启动时AOF(追加写日志)已开启,Redis(内存数据存储)就加载AOF(追加写日志)。原因回到前两节:RDB(快照文件)丢最近几分钟,AOF(追加写日志)默认只丢一秒。[11] [7]
这条规则有一个常见的坑,配置文件专门提醒: 在一个已有数据的实例上改 appendonly 再重启,可能丢数据。 比如实例一直只开RDB(快照文件),现在把 appendonly 改成yes后直接重启:启动时AOF(追加写日志)开着,Redis(内存数据存储)去加载AOF(追加写日志)目录,而目录不存在或为空,于是从空数据集开始,dump.rdb里的数据没有被读取。官方给的正确步骤是:先备份dump.rdb;在运行中的实例上执行 CONFIG SET appendonly yes 动态开启,Redis(内存数据存储)会基于当前内存数据生成AOF(追加写日志);确认写入正常后再更新配置文件(可用 CONFIG REWRITE),否则重启后又是 appendonly no、只加载dump.rdb;官方还有一个可选步骤 CONFIG SET save "" 关闭快照,做了这一步却忘了改配置,dump.rdb就是旧的,开启AOF(追加写日志)以来的写入会全部丢失;重启前用 INFO persistence 确认AOF(追加写日志)重写已经完成。[15]
正常关闭和断电不一样: 配置文件说明,收到SIGTERM(终止信号)正常关闭时,如果配置了save规则,默认会阻塞地写一份RDB(快照文件)再退出;用 SHUTDOWN NOSAVE 可以不写。所以“正常关闭”丢不了几分钟,只有断电、kill -9 这类非正常停止才会。[2]
两种后台任务不会同时跑: 官方说明2.4起,RDB(快照文件)快照进行中不会触发AOF(追加写日志)重写,AOF(追加写日志)重写进行中也不允许BGSAVE(后台保存),避免两个子进程同时做大量磁盘I/O。[11]
备份: RDB(快照文件)文件一旦生成就不再修改,生成过程中用临时文件名、完成后才原子地改名,所以官方说Redis(内存数据存储)“对备份非常友好”,运行中直接复制dump.rdb就行。建议用定时任务每小时、每天各留一份快照,并且每天至少把一份传到机房以外。[12]
拓展:断电时AOF(追加写日志)最后一条命令只写了一半,怎么办?
官方说明:这时AOF(追加写日志)仍然是某个时间点的一致数据(默认策略下最多旧一秒),只是最后一条命令不完整。较新的版本启动时会直接丢弃这条不完整的命令、正常加载并记录日志(配置 aof-load-truncated yes 默认开启)。[15]如果文件不是尾部截断、而是中间出现了无效字节,Redis(内存数据存储)启动时会报错退出,需要用 redis-check-aof 工具修复,修复会丢掉损坏点之后的内容。[13]
先试着自己解释,再看参考回答
两者都开时,重启只加载AOF(追加写日志),因为官方规定AOF(追加写日志)保证最完整,不会去比较哪个文件更新。所以在已有数据的实例上把appendonly改成yes再重启会丢数据,启动时读的是空的AOF(追加写日志)目录;要先用CONFIG SET动态开启、生成AOF(追加写日志)后再改配置文件。RDB(快照文件)快照和AOF(追加写日志)重写不会同时进行,避免两个子进程一起做大量磁盘I/O。RDB(快照文件)文件生成后不再修改,运行中可以直接复制做备份,官方建议定时留快照并传到机房外。
七、怎么选:先问业务能丢多少
“生产环境你会怎么配持久化?”
官方在“那我该用哪个”一节给了三句话:想要和PostgreSQL(一种关系型数据库)相当的数据安全,两个都开;很在意数据但能接受灾难时丢几分钟,只开RDB(快照文件)就够;只开AOF(追加写日志)的用户很多,但官方不鼓励,因为定期有一份RDB(快照文件)快照对备份、快速重启、以及AOF(追加写日志)引擎出bug时都有好处。[14]
把它落到办件系统的三类数据:
| Redis(内存数据存储)里放的是什么 | 丢了会怎样 | 建议 |
|---|---|---|
| 办件详情缓存 | 从MySQL(关系型数据库)重新加载,只是重启后头几分钟数据库压力大一些 | 可以不持久化;或只开RDB(快照文件)让重启后缓存不是空的 |
| 每日受理量、经办人统计,只有Redis(内存数据存储)里有 | 当天数字不准,无法从别处补 | RDB(快照文件)+AOF(追加写日志),appendfsync(追加日志刷盘策略) everysec;最多丢一秒 |
| 登录失败计数、验证码限发 | 锁定被解除、限发失效,持续时间短 | 能接受丢一秒就按上一行;要求更高就把这类状态改存数据库 |
还要把代价说全:开AOF(追加写日志)后文件比RDB(快照文件)大、磁盘写入多;fork(创建子进程)在大实例上有停顿;always几乎不丢但很慢。官方延迟文档给了一个从“最安全”到“最低延迟”的排序:AOF(追加写日志)+always → AOF(追加写日志)+everysec → AOF(追加写日志)+everysec+重写期间不fsync(强制同步到磁盘) → AOF(追加写日志)+no → 只用RDB(快照文件)。[9]
怎样讲清楚: 先说原则(看业务能容忍丢多少、数据能不能从别处恢复),再说官方建议(都开、everysec),最后说代价和取舍。不要只背“RDB(快照文件)快、AOF(追加写日志)全”。
先试着自己解释,再看参考回答
先看Redis(内存数据存储)里放的数据能不能从别处恢复、业务能容忍丢多久。纯缓存可以不持久化或只开RDB(快照文件);只存在Redis(内存数据存储)里的计数、状态,就RDB(快照文件)和AOF(追加写日志)都开,appendfsync(追加日志刷盘策略)用默认的everysec,断电最多丢一秒,这也是官方的建议。只开AOF(追加写日志)官方不鼓励,因为RDB(快照文件)快照对备份和快速重启有用。代价是AOF(追加写日志)文件更大、磁盘写入更多,fork(创建子进程)在大实例上有停顿;要求更高可以用always,但很慢,通常不值得。
八、应用:算一算重启后还剩什么
“给你配置和故障时点,你能说出重启后的数据状态吗?”
A.三个实例,同一次断电
背景: 三个Redis(内存数据存储)实例配置不同,都在10:00:00生成过一次RDB(快照文件)快照(之后没有再触发save规则),10:04:30机器突然断电。从10:00:01起每秒恰好执行一次 INCR stat:accept,到10:04:30共270次;10:00:00时它的值是1000。磁盘和文件都完好,操作系统正常执行了它报告的所有fsync(强制同步到磁盘)。
- 实例甲: 只开RDB(appendonly no)。
- 实例乙: RDB(快照文件)+AOF(追加写日志),appendfsync(追加日志刷盘策略) everysec。
- 实例丙: RDB(快照文件)+AOF(追加写日志),appendfsync(追加日志刷盘策略) no,Linux默认约30秒刷一次。
请回答: 每个实例重启后,stat:accept 大约是多少?各自用什么文件恢复?
完成后核对过程
甲: AOF(追加写日志)没开,加载dump.rdb,回到10:00:00,值为1000。10:00:00之后的270秒写入全部丢失。
乙: AOF(追加写日志)开着,加载AOF(追加写日志)目录,不读dump.rdb。everysec下断电最多丢最近一秒,恢复到约10:04:29,值约1000+269=1269(最后一秒的那一次可能丢)。
丙: 同样加载AOF(追加写日志),但fsync(强制同步到磁盘)交给操作系统、约30秒一次,最坏丢约30秒。值在1240到1270之间,最坏约1240,取决于操作系统最后一次刷盘的时刻。
共同点:乙、丙都比甲至少多保住了四分钟的数据;差别只在AOF(追加写日志)的最后一段停在了第②还是第③阶段。
B.改了配置,重启后数据没了
背景: 线上一个只开RDB(快照文件)的实例,dump.rdb里有完整的统计数据。运维按要求“开启AOF(追加写日志)”,直接把配置文件里的 appendonly no 改成 yes,然后重启了Redis(内存数据存储)。重启后所有键都不见了,dump.rdb文件还在磁盘上、内容完好。
- 为什么数据“没了”?dump.rdb明明还在。
- 正确的开启步骤应该是什么?现在怎样把数据找回来?
完成后核对过程
1. 重启时appendonly是yes,Redis(内存数据存储)按规则加载AOF(追加写日志)目录,而这个实例从没写过AOF(追加写日志),目录是空的,于是从空数据集启动;dump.rdb虽然完好,但AOF(追加写日志)开着时不会去读它。配置文件对此专门有提醒。
2. 官方步骤:先备份dump.rdb;在运行中的实例上执行 CONFIG SET appendonly yes,Redis(内存数据存储)会基于当前内存数据生成AOF(追加写日志)文件;确认写入正常后更新配置文件;重启前用 INFO persistence 确认 aof_rewrite_in_progress 和 aof_rewrite_scheduled 都为0、aof_last_bgrewrite_status 为ok,之后重启才安全。找回数据按顺序做:①立刻把dump.rdb复制到别处,因为save规则仍然生效,自动快照或正常关闭都会用现在的空数据覆盖它;②用 SHUTDOWN NOSAVE 停止;③配置改回appendonly no,把备份的dump.rdb放回去再启动;④再按上面的官方步骤开启AOF(追加写日志)。重启后这段时间新写入的数据需要另行补录。
九、即时检验:先判断,再读解释
每题只问一个主要判断。先作答,再展开核对答案与解释。
- 快照期间的新写入
10:00:00执行BGSAVE(后台保存),子进程开始写文件;10:00:03父进程收到并执行了 INCR stat:accept;10:00:10子进程写完并替换了dump.rdb。
问题: 这次INCR的结果在新的dump.rdb里吗?
- A. 在,子进程会同步修改
- B. 不在,只有开始那一刻
- C. 在,写完前都会收进去
查看答案与解释
答案:B
子进程看到的是fork(创建子进程)那一刻(10:00:00)的数据;之后父进程的修改只改父进程自己的内存页(写时复制),不进这次快照。
- everysec 断电
AOF(追加写日志)开启,appendfsync(追加日志刷盘策略) everysec,操作系统和磁盘正常。Redis(内存数据存储)持续写入,机器突然断电。
问题: 官方说法下,最多丢失多少?
- A. 最近约一秒的写入
- B. 最近约两秒的写入
- C. 最近约三十秒写入
查看答案与解释
答案:A
官方对everysec的说法是“可能丢一秒的数据”。“两秒”是fsync(强制同步到磁盘)未完成时推迟write的上限,说的是延迟不是丢失量;“约30秒”是appendfsync(追加日志刷盘策略) no的情况。
- 两个都开时重启
RDB(快照文件)和AOF(追加写日志)都开启,appendfsync(追加日志刷盘策略) no。10:04:20 BGSAVE(后台保存)已完成落盘,AOF(追加写日志)的内容只刷到10:04:00;10:04:30断电后重启。
问题: Redis(内存数据存储)用哪个文件重建数据?
- A. 比较后用更新的那一个
- B. 两份文件拼接起来使用
- C. 只读日志目录里的文件
查看答案与解释
答案:C
官方规则:AOF(追加写日志)开启时就加载AOF(追加写日志),没有比较两个文件谁更新的逻辑,也不会把dump.rdb和AOF(追加写日志)拼起来用。这个例子里dump.rdb确实更新,但重启后仍回到AOF(追加写日志)落盘的10:04:00。
- 为什么要重写
一个计数器一天内被INCR了50000次,AOF(追加写日志)里就有50000条INCR记录。假设 aof-use-rdb-preamble 设为no,基础文件用命令格式。
问题: 重写后AOF(追加写日志)里和这个键相关的内容变成什么?
- A. 恢复当前值的最短命令
- B. 把五万条记录压缩保存
- C. 只保留最早的一条记录
查看答案与解释
答案:A
重写按当前内存数据生成“重建当前数据集所需的最短命令序列”,对这个键只需要一条把它设成当前值的命令,不是对旧记录做压缩。
- 改配置后重启
一个只开RDB(快照文件)、有完整数据的实例。运维直接把配置文件里的appendonly改为yes并重启。
问题: 重启后会看到什么?
- A. 数据完整并自动生成日志
- B. 数据为空且快照未被读取
- C. 启动失败并报告缺少日志
查看答案与解释
答案:B
AOF(追加写日志)开启时只加载AOF(追加写日志)目录,目录为空就从空数据集启动,不读dump.rdb。应先在运行中用CONFIG SET appendonly yes生成AOF(追加写日志),再改配置文件。
- 纯缓存实例
一个Redis(内存数据存储)实例只存办件详情缓存,所有数据都能从MySQL重新加载;团队希望尽量减少磁盘写入和fork(创建子进程)停顿。
问题: 按官方建议和业务情况,最合理的选择是?
- A. 都开启并把刷盘设为总是
- B. 只开追加日志并每秒刷盘
- C. 快照和追加日志全部关闭
查看答案与解释
答案:C
数据能从别处恢复、又想少写磁盘,官方列的“不持久化”正是为缓存场景准备的,也不再有fork(创建子进程)停顿。想让重启后缓存不为空,可以只开RDB(快照文件),但那仍要fork(创建子进程),是另一种折中。只开AOF(追加写日志)官方不鼓励,always更是没必要。
十、把过程讲出来:用完整回答检验理解
连续回答下面四问,可以写下来,也可以先口述再整理成文字。每问先说是什么,再说为什么,最好带一个办件系统的例子和具体数字。写完再对照各节的参考回答。
第一问:RDB(快照文件)和AOF(追加写日志)的区别
请比较Redis(内存数据存储)的两种持久化方式。
请回答: 各自记的是什么?各自怎么生成?说到fork(创建子进程)、子进程和追加写入。优缺点各两条,并说明断电时各丢多少。
第二问:AOF(追加写日志)的刷盘策略
常见问题:“appendfsync(追加日志刷盘策略)有哪几档?你会选哪个?everysec到底会丢多少?”
请回答: 用“命令停在哪个阶段”解释三档的差别;说出官方对everysec的说法;再说明“Redis(内存数据存储)进程崩溃”和“整机断电”为什么丢的不一样。
第三问:AOF(追加写日志)重写与7.0的变化
常见问题:“AOF(追加写日志)会不会无限变大?重写期间的新写入怎么处理?7.0改了什么?”
请回答: 为什么要重写、怎样触发;7.0以前重写期间的新命令放哪、有什么问题;7.0的基础文件+增量文件怎样解决;基础文件默认用什么格式、为什么。
第四问:两个都开时的重启与配置建议
常见问题:“同时开RDB(快照文件)和AOF(追加写日志),重启读哪个?生产环境怎么配?”
请回答: 重启规则和原因;在已有实例上开启AOF(追加写日志)的正确步骤;办件系统又新增两类数据:大厅叫号屏的“当前号码”(丢了重新叫号即可),和一个用SET NX实现的审批互斥锁。分别给出持久化建议并说明理由和代价。
练习:请在自己的笔记中写出答案(可附第八节的计算过程),再对照各节参考回答核对。
十一、集中追问:把容易混的地方分开
先自己解释,再展开核对
BGSAVE(后台保存)和SAVE有什么区别?生产上能用SAVE吗?
BGSAVE(后台保存)在后台fork(创建子进程)子进程保存,父进程继续服务,命令立刻返回OK。SAVE在主线程同步保存,期间所有客户端都被阻塞;官方说生产环境几乎永远不该用SAVE。
Redis(内存数据存储)正常关闭(SIGTERM)时会不会先保存?
配置文件说明:收到SIGINT或SIGTERM时,如果配置了save规则,默认会阻塞地写一份RDB(快照文件)再退出。所以“正常关闭”和“断电、kill -9”丢的数据不一样,只开RDB(快照文件)时,后者才会丢最近几分钟。
appendfsync(追加日志刷盘策略) always 是不是完全不丢数据?
官方只说它“非常安全”,并且说明多个并行写会合并成一次fsync(强制同步到磁盘)、在回复客户端之前完成。本课不把它说成“零丢失”,因为官方没有这样承诺,磁盘本身是否如实完成同步也是前提。
MySQL的 innodb_flush_log_at_trx_commit=1 和 Redis(内存数据存储) 的 appendfsync(追加日志刷盘策略) always 是一回事吗?
思路相同:都是“回复成功之前把日志可靠同步到磁盘”,对应第14课的第③阶段。区别是MySQL按事务提交同步,Redis(内存数据存储)按一批命令同步。everysec则对应MySQL参数为2时“交给操作系统、后台定期同步”的思路。
AOF(追加写日志)重写的时候,RDB(快照文件)快照会不会同时进行?
不会。官方说明2.4起,快照进行中不触发AOF(追加写日志)重写,重写进行中不允许BGSAVE(后台保存),避免两个子进程同时做大量磁盘I/O。
十二、隔日复测:换一个业务对象
隔一天再用五分钟回答;约一周后再换场景试一次。刚学完马上复述,只能说明短时记忆。
1. 商城用Redis(内存数据存储)记每件商品的今日浏览量,只开RDB(快照文件),save规则是默认三档。某商品从14:00开始每秒被浏览一次,14:00:00时RDB(快照文件)刚保存过、值为500。14:03:00断电,期间没有触发save规则。重启后值是多少?为什么?
500。只开RDB(快照文件),重启加载14:00:00的dump.rdb,之后180秒的浏览全部丢失。要少丢,需要开AOF(默认everysec最多丢一秒)。
2. 上题改成RDB(快照文件)+AOF(追加写日志)、appendfsync(追加日志刷盘策略) everysec,其他条件不变。重启后值大约是多少?用什么文件恢复?
约679(500+179,最后一秒可能丢)。AOF(追加写日志)开启,加载AOF(追加写日志)目录,不读dump.rdb。
3. 一个只开RDB(快照文件)的实例要改成开AOF(追加写日志),正确的操作顺序是什么?直接改配置重启会怎样?
先在运行中执行 CONFIG SET appendonly yes,等AOF(追加写日志)生成后再改配置文件、重启。直接改配置重启,启动时只加载空的AOF(追加写日志)目录,数据为空。
第16课复习:INCR 和 EXPIRE 是两条命令,中间崩溃会怎样?官方建议怎么解决?
INCR执行了、EXPIRE没执行,键就没有过期时间,一直留着(登录失败计数会把账号永久锁住)。官方建议把“INCR,返回1时EXPIRE”写成一段Lua脚本作为一条命令执行。
官方来源与配套材料
本课速查 · 完整术语表。时间点、计数值均为教学设定;本课没有实际做断电或重启实验。
Snapshotting、redis.conf 8.10.2 的 save 说明、BGSAVE(后台保存)、SAVE:dump.rdb、save规则默认值、fork(创建子进程)与写时复制。
RDB(快照文件) advantages/disadvantages:紧凑、重启快、丢最近几分钟、fork(创建子进程)停顿。
延迟诊断:Latency generated by fork(创建子进程):页表复制与官方给出的实测数字。
Append-only file:快照不够耐久、appendonly yes、7.0多文件结构。
AOF(追加写日志) advantages/disadvantages:更耐久、只追加、文件更大、7.0前重写代价。
redis.conf 8.10.2:appendonly、文件名与目录、自动重写阈值、aof-use-rdb-preamble(4.0.0默认no、5.0.0起yes)、改配置重启的提醒。
How durable is the append only file?与redis.conf:三档策略、断电丢一秒、进程崩溃丢一条。
延迟诊断:AOF(追加写日志)与磁盘I/O:耐久性与延迟的排序、everysec推迟write最多两秒。
Log rewriting:计数器例子、BGREWRITEAOF(后台重写追加日志)、7.0重写流程。
Interactions between AOF(追加写日志) and RDB(快照文件):重启用AOF(追加写日志)、两种后台任务不并发。
Backing up Redis(内存数据存储) data:运行中复制RDB(快照文件)安全、定时快照与异地保存。
AOF(追加写日志)截断与损坏:丢弃不完整命令、redis-check-aof。
Ok, so what should I use?:官方选型建议。
How I can switch to AOF(追加写日志)与redis.conf:运行中动态开启、更新配置、重启前等重写完成;aof-load-truncated。
JavaGuide:Redis(内存数据存储)持久化机制详解(第三方):“混合持久化”等常见叫法;其中“everysec最多丢2秒”“4.0–6.x默认关闭”与官方说法不一致:官方对everysec丢失量的说法是一秒,redis.conf中 aof-use-rdb-preamble 自5.0.0起默认yes(见来源[8]、[7])。
学完后,沿着同一个办件系统的时间线检查自己能否讲清“子进程为什么看不到父进程后来的修改”“everysec的一秒是从哪一刻算起”,而不只是背出“RDB(快照文件)快、AOF(追加写日志)全”。记下仍不确定的地方,隔日回看时先尝试回答,再核对课程里的解释。本课末尾的课后问答记录了几处容易说错的地方。
课后问答:理解是怎样变化的
问题与结论默认可见。展开后可看理解过程;以下根据真实交流整理,并非逐字稿。
RDB(快照文件)和AOF(追加写日志)都开、appendfsync(追加日志刷盘策略)为no;dump.rdb比AOF(追加写日志)已落盘的内容更新。断电重启后,Redis(内存数据存储)用哪个文件重建数据?
最终结论: AOF(追加写日志)开着就只读AOF(追加写日志)目录里的文件。即使dump.rdb更新,重启后仍回到AOF(追加写日志)已落盘的状态。
展开理解过程与易混点
最初理解(归纳): 曾选择“两份文件拼接起来使用”。
纠正与解释: 官方没有“比较谁更新”或“拼接”的逻辑。“拼接”容易和另一件事混淆:AOF(追加写日志)目录里先读基础文件(快照文件格式)、再重放增量文件,这是AOF(追加写日志)自己内部的加载顺序,不是把dump.rdb和AOF(追加写日志)拼起来。
最终结论: AOF(追加写日志)开着就只读AOF(追加写日志)目录里的文件。即使dump.rdb更新,重启后仍回到AOF(追加写日志)已落盘的状态。
现有复盘未逐条保留中间追问,本页不补写对话。
正常关闭、Redis(内存数据存储)进程崩溃(或 kill -9)、整机断电,在everysec下各丢多少?为什么不同?
最终结论: 正常关闭(SIGTERM/SHUTDOWN):配置了save规则时先阻塞地写一份RDB(快照文件)再退出,不丢。进程崩溃或 kill -9:最多丢最后一批还没write给操作系统的写,官方说只丢一条。整机断电:操作系统缓存一起丢,只有已fsync(强制同步到磁盘)的还在,最多丢约一秒。
展开理解过程与易混点
最初理解(归纳): 曾认为Redis(内存数据存储)自己崩溃或被强制关闭时,会收到一个信号,然后临时写一个RDB(快照文件)。
纠正与解释: 这把三种情况混在了一起。只有正常关闭有信号处理的机会;崩溃和 kill -9 进程立刻消失,来不及写RDB(快照文件)。进程崩溃只丢一条,原因不是临时写了RDB(快照文件),而是Redis(内存数据存储)每轮回复客户端之前已经把命令write给操作系统,进程没了、操作系统还活着,这些数据会被它刷到磁盘。
最终结论: 正常关闭(SIGTERM/SHUTDOWN):配置了save规则时先阻塞地写一份RDB(快照文件)再退出,不丢。进程崩溃或 kill -9:最多丢最后一批还没write给操作系统的写,官方说只丢一条。整机断电:操作系统缓存一起丢,只有已fsync(强制同步到磁盘)的还在,最多丢约一秒。
现有复盘未逐条保留中间追问,本页不补写对话。
Redis(内存数据存储) 7.0起,AOF(追加写日志)重写期间的新写入由谁记录?重写完成后怎样切换?
最终结论: 7.0起,安排重写时父进程打开一个新的增量文件(incr)继续追加,子进程生成新的基础文件(base);新base加新incr已经是完整的一套,用临时清单文件原子地切换过去,旧的base和incr随后可以删除。7.0的改变正是去掉了重写缓冲和双写。
展开理解过程与易混点
最初理解(归纳): 曾认为7.0起重写期间由子线程持续记录新数据,最后原子地和源文件合并;也说过“新写入先记录、重写完成后再原子追加”,这其实是7.0以前的流程。
纠正与解释: 记录新写入的不是子线程,而是父进程本身;子进程只按fork(创建子进程)那一刻的数据生成新的基础文件。最后也没有“合并”这一步。7.0以前,重写期间的新命令攒在内存缓冲区,完成后追加到新文件末尾,占内存且这些命令要写两次。
最终结论: 7.0起,安排重写时父进程打开一个新的增量文件(incr)继续追加,子进程生成新的基础文件(base);新base加新incr已经是完整的一套,用临时清单文件原子地切换过去,旧的base和incr随后可以删除。7.0的改变正是去掉了重写缓冲和双写。
现有复盘未逐条保留中间追问,本页不补写对话。
appendfsync(追加日志刷盘策略)everysec 最多丢多少?是“最多2秒”吗?
最终结论: 官方对丢失量的说法是约一秒。回答时先说官方的一秒,再补“磁盘很慢时可能超过一秒”这个边界,不要直接说“最多2秒”。
展开理解过程与易混点
最初理解(归纳): 曾认为everysec最多丢2秒。
纠正与解释: “两秒”出自官方延迟文档,指fsync(强制同步到磁盘)还没做完时,Redis(内存数据存储)会把下一次write最多推迟两秒,说的是延迟,不是丢失量。被推迟的命令还在Redis(内存数据存储)内存里,所以磁盘很慢时实际丢失可能超过一秒,但官方没有给出“两秒”这个上限。
最终结论: 官方对丢失量的说法是约一秒。回答时先说官方的一秒,再补“磁盘很慢时可能超过一秒”这个边界,不要直接说“最多2秒”。
现有复盘未逐条保留中间追问,本页不补写对话。
appendfsync(追加日志刷盘策略)always 是不是完全不丢数据?
最终结论: 说“几乎不丢”:always每批命令写入后立刻fsync(强制同步到磁盘),在回复客户端之前完成,所以最安全,但不要说成“零丢失”。
展开理解过程与易混点
最初理解(归纳): 曾认为always不会丢失。
纠正与解释: 官方只说“very safe”,没有承诺零丢失;磁盘是否如实完成fsync(强制同步到磁盘)也是前提。
最终结论: 说“几乎不丢”:always每批命令写入后立刻fsync(强制同步到磁盘),在回复客户端之前完成,所以最安全,但不要说成“零丢失”。
现有复盘未逐条保留中间追问,本页不补写对话。
追求最高安全时用 always+RDB(快照文件),主要代价是什么?
最终结论: always的主要代价是每批命令都要等fsync(强制同步到磁盘)完成才回复,吞吐大幅下降;RDB(快照文件)的代价是fork(创建子进程)在大实例上的停顿。
展开理解过程与易混点
最初理解(归纳): 曾把代价说成“子线程开启多”。
纠正与解释: 这里有两处不准:fork(创建子进程)出来的是子进程,不是子线程;而且always的代价也不在于进程多。
最终结论: always的主要代价是每批命令都要等fsync(强制同步到磁盘)完成才回复,吞吐大幅下降;RDB(快照文件)的代价是fork(创建子进程)在大实例上的停顿。
现有复盘未逐条保留中间追问,本页不补写对话。
在一个只开RDB(快照文件)、已有数据的实例上开启AOF(追加写日志),正确步骤和命令是什么?
最终结论: 先备份dump.rdb;在运行中的实例上执行 CONFIG SET appendonly yes,让Redis(内存数据存储)基于当前内存数据生成AOF(追加写日志);确认写入正常后更新配置文件(可用 CONFIG REWRITE);重启前用 INFO persistence 确认重写已经完成。
展开理解过程与易混点
最初理解(归纳): 曾说出了完整流程(不能直接改配置重启,要先动态开启、确认后再改配置文件),但没有记住动态开启的命令名。
纠正与解释: 直接把配置改成 appendonly yes 再重启,启动时会去加载空的AOF(追加写日志)目录,从空数据集开始,dump.rdb不会被读取。
最终结论: 先备份dump.rdb;在运行中的实例上执行 CONFIG SET appendonly yes,让Redis(内存数据存储)基于当前内存数据生成AOF(追加写日志);确认写入正常后更新配置文件(可用 CONFIG REWRITE);重启前用 INFO persistence 确认重写已经完成。
现有复盘未逐条保留中间追问,本页不补写对话。