
持久化
持久化的核心本质是让数据在进程重启、服务器宕机或断电等异常场景下依然能够保留 ,确保数据不会因为运行环境的波动而丢失。而作为内存数据库的代表,Redis 的数据默认全部存储在内存中,虽然内存读写拥有极致的 QPS 和低延迟特性,但内存是易失性存储,一旦断电、进程退出或服务器重启,所有数据会瞬间清空。为了解决这个问题,Redis 必须通过持久化机制把内存中的数据快照或操作日志保存到硬盘,作为 "冷数据" 进行沉淀,在下次重启时再读取这些持久化文件将数据重新加载回内存,实现数据的持久存活。
这一机制和关系型数据库 MySQL 的 ACID 四大特性 中的**持久性(Durability)**有着本质的同源逻辑与区别。
- MySQL 的 ACID 与持久性 :MySQL 作为磁盘数据库,ACID 特性里的 "持久性" 指的是一旦事务提交成功,其所做的修改就会永久保存到数据库中,即使系统崩溃,提交的数据也不会丢失。这种持久性通常依赖磁盘日志(Redo Log、Binlog)和存储引擎的刷盘策略来实现。
- Redis 持久化的逻辑:Redis 的持久化正是为了实现这一 "持久性" 目标而存在的。因为 Redis 依赖内存,必须通过额外的持久化手段(RDB 或 AOF)来保证 "即使重启 Redis,数据也不会丢",这是内存数据库对 ACID 持久性特性的一种补充和实现。
基于 "避免数据丢失" 的核心目标,Redis 提供了 RDB 和 AOF 两种截然不同的持久化方案,以此来平衡数据安全与读写性能:
- RDB(快照模式) :相当于给 Redis 拍 "照片",在指定的时间点把内存中的全量数据生成紧凑的二进制快照文件。它的优势是恢复速度极快,文件体积小,但缺点是无法做到实时持久化,可能会丢失快照间隔内的数据。
- AOF(日志模式) :相当于记 "日记",以独立日志的方式记录每一条修改 Redis 数据的写命令。重启时通过重放这些命令来恢复数据,它解决了 RDB 实时性差的问题,数据安全性更高,但文件体积较大,恢复速度相对慢一些。
本篇内容:
- 介绍 RDB、AOF 的配置和运行流程,以及控制持久化的命令,如
bgsave和bgrewriteaof。 - 对常见持久化问题进行分析定位和优化。
RDB
RDB 持久化是把当前进程数据生成快照保存到硬盘的过程,触发 RDB 持久化过程分为手动触发 和自动触发。
触发机制
手动触发分别对应 save 和 bgsave 命令:
- save 命令:阻塞当前 Redis 服务器,直到 RDB 过程完成为止,对于内存比较大的实例会造成长时间阻塞,基本不采用 。
- bgsave 命令:Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,一般时间很短。
Redis 内部的所有涉及 RDB 的操作都采用类似 bgsave 的方式。
除了手动触发之外,Redis 运行自动触发 RDB 持久化机制,这个触发机制才是在实战中有价值的:【自动触发 RDB 持久化操作的三个场景】
- 使用 save 配置。如 save m n 表示 m 秒内数据集发生了 n 次修改,自动 RDB 持久化。【配置文件中】【/etc/redis/redis.conf】
我们可以设置为 save "" 来表示禁止自动生成快照!

-
从节点进行全量复制操作时,主节点自动进行 RDB 持久化,随后将 RDB 文件内容发送给从节点。【主从复制时】
-
执行 shutdown 命令关闭 Redis 时,执行 RDB 持久化。【手动关闭 Redis 服务端】
流程说明
bgsave 是主流的 RDB 持久化方式,下面是它的运作流程:

- 执行 bgsave 命令,Redis 父进程判断当前进程是否存在其他正在执行的子进程,如 RDB/AOF 子进程,如果存在,bgsave 命令直接返回。
- 父进程执行 fork 创建子进程,fork 过程中父进程会阻塞,通过 info stats 命令查看 latest_fork_usec 选项,可以获取最近一次 fork 操作的耗时,单位为微秒。
- 父进程 fork 完成后,bgsave 命令返回 Background saving started 信息并不再阻塞父进程,可以继续响应其他命令。
- 子进程创建 RDB 文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。 执行 lastsave 命令可以获取最后一次生成 RDB 的时间,对应 info 统计的 rdb_last_save_time 选项。
- 进程发送信号给父进程表示完成,父进程更新统计信息。
Linux 的 fork 机制通过**写时拷贝(Copy-On-Write, COW)**创建子进程,子进程会与父进程共享同一份内存快照,只有当父 / 子进程尝试修改某块内存时,才会真正复制对应的内存页,而非一次性全量拷贝父进程内存;
Redis 正是利用这一特性,在执行 bgsave(RDB)时 fork 出子进程,让子进程基于共享的内存快照在后台完成持久化写入磁盘的操作,主进程仅在 fork 阶段短暂阻塞,之后可继续响应客户端请求,既避免了全量内存拷贝的高额开销,又保证了主进程处理请求的性能与实时性;
相比多线程方案,多进程 + 写时拷贝的优势在于:子进程拥有独立的地址空间,持久化过程中不会因线程安全问题干扰主进程数据,且写时拷贝大幅降低了 fork 的内存与时间成本,让后台持久化几乎不影响主进程的读写性能,同时规避了多线程并发修改内存可能导致的数据不一致风险。
RDB 文件的处理
保存 :RDB 文件保存于 dir 配置指定的目录(默认 /var/lib/redis/)下,文件名通过 dbfilename 配置(默认 dump.rdb)指定。可以通过执行 config set dir {newDir} 和 config set dbfilename {newFilename} 在运行期间动态执行,当下次运行时 RDB 文件会保存到新目录。

当已有一份 dump.rdb 快照文件时,执行 bgsave 的更新流程是这样的:Redis 主进程先 fork 出子进程,子进程基于 fork 时的内存快照生成新的临时 RDB 文件 (不会直接覆盖旧文件),待子进程完整生成新 RDB 并写入磁盘后,会通过信号通知父进程,父进程随后执行原子替换操作 ------ 用新的临时 RDB 文件直接覆盖旧的 dump.rdb,整个过程保证任何时刻磁盘上都有一份完整可用的 RDB 文件,避免了中途宕机导致旧文件损坏,同时子进程依赖 Linux 写时拷贝(COW)机制共享父进程内存,仅在父进程修改内存页时才复制对应页,大幅降低持久化开销,主进程仅在 fork 阶段短暂阻塞,之后可继续处理客户端请求,不影响业务性能。
如果已经有旧的 dump.rdb,执行 save 的更新逻辑是这样一段话:
执行 save 时,Redis 主进程会直接阻塞所有客户端请求 ,在当前目录先生成一份新的临时 RDB 文件,等这份新文件完全写完后,再原子替换掉旧的 dump.rdb ;整个过程不使用 fork、不使用子进程、也没有写时拷贝,完全由主进程同步完成,所以在快照生成和文件替换期间,Redis 无法处理任何读写命令,数据越大阻塞时间越长,虽然流程简单、文件替换同样安全,但因为严重影响服务可用性,生产环境绝对不推荐使用。
我们可以进行比较文件 inode 来实现测试,我们有一个命令是:stat dump.rdb
bash
lfz@U22:/var/lib$ sudo -i
root@U22:~# cd /var/lib/redis
root@U22:/var/lib/redis# l
dump.rdb
root@U22:/var/lib/redis# stat dump.rdb
File: dump.rdb
Size: 93 Blocks: 8 IO Block: 4096 regular file
Device: 803h/2051d Inode: 1442076 Links: 1
Access: (0660/-rw-rw----) Uid: ( 131/ redis) Gid: ( 138/ redis)
Access: 2026-03-29 21:51:46.104503841 +0800
Modify: 2026-03-29 21:51:46.105503807 +0800
Change: 2026-03-29 21:51:46.107503739 +0800
Birth: 2026-03-29 21:51:46.104503841 +0800

压缩 :Redis 默认采用 LZF 算法对生成的 RDB 文件做压缩处理,压缩后的文件远远小于内存大小,默认开启,可以通过参数 config set rdbcompression {yes|no} 动态修改。
💡 虽然压缩 RDB 会消耗 CPU,但可以大幅降低文件的体积,方便保存到硬盘或通过网络发送到从节点,因此建议开启。
校验 :如果 Redis 启动时加载到损坏的 RDB 文件会拒绝启动。这时可以使用 Redis 提供的 redis-check-dump 工具检测 RDB 文件并获取对应的错误报告。
1. 损坏后的行为表现
结果不可预期:Redis 服务器可能正常启动,但数据可能部分正确 / 异常;也可能直接启动失败。
损坏位置决定影响:
- 若损坏在文件末尾:对前面数据无影响,Redis 可正常启动并读取有效 key。
- 若损坏在文件中间:会导致 Redis 启动失败,数据加载中断。
重启方式注意事项 :必须通过 kill 进程方式重启 Redis;若用 service redis-server restart,服务器退出时会自动生成新 RDB 快照,覆盖已损坏的文件。
2. 故障排查方法
查看日志定位问题:
- 日志路径默认
/var/log/redis/(可在配置文件中修改),核心日志文件为redis-server.log。 - 典型报错示例:
Unrecoverable error, aborting now、Unexpected EOF reading RDB file,指向 RDB 读取失败。
使用官方检查工具:
- Redis 5.0 版本中,
redis-check-rdb与redis-server是同一可执行程序,通过传入 RDB 文件参数即可启动检查模式(不会真正启动 Redis 服务)。 - 命令示例:
redis-check-rdb dump.rdb,工具会输出具体错误位置(如偏移量、读取失败的 key)。
RDB 是二进制格式文件,损坏后的影响取决于损坏位置:末尾损坏影响小,中间损坏会导致服务无法启动。可通过查看 Redis 日志 和使用 redis-check-rdb 工具 来定位和分析损坏原因,同时要注意避免用 service restart 方式覆盖损坏文件。
RDB 的优缺点
优点:
- RDB 是一个紧凑压缩的二进制文件,代表 Redis 在某个时间点上的数据快照。非常适用于备份、全量复制等场景。比如每 6 小时执行
bgsave备份,并把 RDB 文件复制到远程机器或者文件系统中(如 HDFS)用于灾备。 - Redis 加载 RDB 恢复数据远远快于 AOF 的方式。
缺点:
- RDB 方式数据没办法做到实时持久化 / 秒级持久化。因为 bgsave 每次运行都要执行 fork 创建子进程,属于重量级操作,频繁执行成本过高。【我们可以进行 kill 命令,进行测试】
- RDB 文件使用特定二进制格式保存,Redis 版本演进过程中有多个 RDB 版本,兼容性可能有风险。
AOF
AOF(Append Only File)持久化:以独立日志的方式记录每次写命令,重启时再重新执行 AOF 文件中的命令达到恢复数据的目的。**AOF 的主要作用是解决了数据持久化的实时性,目前已经是 Redis 持久化的主流方式。**理解掌握好 AOF 持久化机制对我们兼顾数据安全性和性能非常有帮助。
使用 AOF
开启 AOF 功能需要设置配置:appendonly yes,默认不开启。所以我们需要对配置文件进行修改,然后必须要进行服务器的重启才可以达到效果!
bash
sudo systemctl restart redis-server
AOF 文件名通过appendfilename配置(默认是appendonly.aof)设置。保存目录同 RDB 持久化方式一致,通过dir配置指定。


bash
root@U22:/var/lib/redis# l
appendonly.aof dump.rdb
AOF 的工作流程操作:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load),如下图所示。

- 所有的写入命令会追加到
aof_buf(缓冲区)中。 - AOF 缓冲区根据对应的策略向硬盘做同步操作。
- 随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
- 当 Redis 服务器启动时,可以加载 AOF 文件进行数据恢复。
命令写入
AOF 命令写入的内容直接是文本协议格式 。例如set hello world这条命令,在 AOF 缓冲区会追加如下文本:
bash
*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n

此处遵守 Redis 格式协议,Redis 选择文本协议可能的原因:文本协议具备较好的兼容性;实现简单;具备可读性。
AOF 过程中为什么需要aof_buf这个缓冲区?Redis 使用单线程响应命令,如果每次写 AOF 文件都直接同步硬盘,性能从内存的读写变成 IO 读写,必然会下降。先写入缓冲区可以有效减少 IO 次数,同时,Redis 还可以提供多种缓冲区同步策略,让用户根据自己的需求做出合理的平衡。
文件同步
Redis 提供了多种 AOF 缓冲区同步文件策略,由参数appendfsync控制,不同值的含义如下表所示。
AOF 缓冲区同步文件策略
| 可配置值 | 说明 |
|---|---|
always |
命令写入aof_buf后调用fsync同步,完成后返回 |
everysec |
命令写入aof_buf后只执行write操作,不进行fsync。每秒由同步线程进行fsync |
no |
命令写入aof_buf后只执行write操作,由 OS 控制fsync频率 |
系统调用write和fsync说明
- write操作会触发延迟写(delayed write)机制。Linux 在内核提供页缓冲区用来提供硬盘 IO 性能。write操作在写入系统缓冲区后立即返回。同步硬盘操作依赖于系统调度机制,例如:缓冲区页空间写满或达到特定时间周期。同步文件之前,如果此时系统故障宕机,缓冲区内数据将丢失。
- fsync针对单个文件操作,做强制硬盘同步,fsync将阻塞直到数据写入到硬盘。
不同配置的影响
- 配置为
always时,每次写入都要同步 AOF 文件,性能很差,在一般的 SATA 硬盘上,只能支持大约几百 TPS 写入。除非是非常重要的数据,否则不建议配置。 - 配置为
no时,由于操作系统同步策略不可控,虽然提高了性能,但数据丢失风险大增,除非数据重要程度很低,一般不建议配置。 - 配置为everysec,是默认配置,也是推荐配置,兼顾了数据安全性和性能。理论上最多丢失 1 秒的数据。
重写机制
AOF(Append Only File)持久化的本质是:把每一条写命令追加到日志文件末尾,Redis 重启时,通过重放这些命令恢复数据。
但这个机制有个致命问题:AOF 文件会持续膨胀。
- 中间过程的冗余命令会被完整记录,但 Redis 重启只关心最终数据状态,冗余命令只会拖慢启动速度、浪费磁盘空间。
重写后 AOF 文件变小的原因
- 进程内已超时的数据不再写入文件。
- 旧的 AOF 中的无效命令,例如
del、hdel、srem等重写后将会删除,只需要保留数据的最终版本。 - 多条写操作合并为一条,例如
lpush list a、lpush list b、lpush list c可以合并为lpush list a b c。
较小的 AOF 文件一方面降低了硬盘空间占用,另一方面可以提升启动 Redis 时数据恢复的速度。
给个示例:
| 客户端执行的原始命令(AOF 会完整记录) | 重写后等价的最终命令(只保留结果) |
|---|---|
lpush key 111 |
lpush key 111 222 333 |
lpush key 222 |
|
lpush key 333 |
原理 :3 次 lpush 的最终结果是 key 中存储 [333, 222, 111],重写时直接用一条命令还原最终状态,删除 2 条中间冗余命令。
触发方式
AOF 重写过程可以手动触发和自动触发:
手动触发 :调用bgrewriteaof命令。
自动触发 :根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机。
auto-aof-rewrite-min-size:表示触发重写时 AOF 的最小文件大小,默认为 64MB。auto-aof-rewrite-percentage:代表当前 AOF 占用大小相比较上次重写时增加的比例。
重写流程(bgrewriteaof)

1. 触发重写,创建子进程
当执行 BGREWRITEAOF 命令(手动触发)或满足自动重写条件时,Redis 主线程(父进程)执行 fork 操作,创建一个子进程。
- 关键特性 :
fork瞬间,子进程完整继承父进程的内存数据快照(即 fork 时刻的数据库状态),后续父进程的新操作不会同步给子进程。 - 父进程继续正常接收、处理客户端的读写请求,完全不阻塞服务。
2. 子进程生成新 AOF 文件
子进程基于继承的内存快照,遍历所有键值对,用最少的命令(去冗余、合并操作)生成全新的 AOF 文件。
- 这个过程和 RDB 生成快照逻辑一致:都是基于内存状态生成文件,区别仅在于 RDB 是二进制格式,AOF 重写是文本命令格式。
- 子进程只写新文件,不修改旧 AOF 文件,保证重写过程中旧文件始终可用。
3. 父进程处理新请求,写入专用缓冲区
子进程重写期间,父进程收到的新写请求,会同时做两件事:
- 正常写入旧 AOF 文件(保证持久化不中断,即使重写失败也不丢数据);
- 额外写入
aof_rewrite_buf重写缓冲区(专门记录 fork 之后的新操作,解决子进程内存快照过时的问题)。
4. 子进程完成重写,通知父进程
子进程写完新 AOF 文件后,通过信号通知父进程。
- 此时新文件仅包含 fork 时刻的内存数据,缺少 fork 之后的新操作,需要父进程补全。
5. 父进程补全缓冲区数据,原子替换旧文件
父进程收到通知后,执行最后三步:
- 将
aof_rewrite_buf中的所有新操作,追加写入新 AOF 文件,保证新文件数据与当前内存完全一致; - 原子性地用新 AOF 文件替换旧 AOF 文件 (Linux
rename操作,无中间状态,保证数据一致性); - 后续所有新写请求,直接写入新的 AOF 文件,重写流程彻底完成。
相关问题
1. 为什么用 fork 子进程?
- 避免阻塞主线程:重写是 CPU/IO 密集型操作,子进程执行不影响父进程处理客户端请求。
- 利用「写时复制(COW)」机制:
fork不立即复制内存,仅复制页表,只有父进程修改内存页时才复制,大幅降低内存开销。
2. 为什么需要 aof_rewrite_buf 缓冲区?
- 子进程的内存快照是 fork 时刻的状态,fork 之后的新操作子进程无法感知。
- 用缓冲区记录新操作,最终追加到新文件,保证新 AOF 文件的数据完整性,不会丢失重写期间的任何写请求。
3. 重写的原子性保障
- 旧文件替换为新文件是原子操作,不会出现「新旧文件混用」「数据不一致」的问题,即使重写过程中宕机,重启后仍可通过旧 AOF 文件恢复数据。
4. 与 RDB 持久化的对比
| 特性 | AOF 重写 | RDB 持久化 |
|---|---|---|
| 生成方式 | 基于内存,生成文本命令格式的新 AOF 文件 | 基于内存,生成二进制格式的 RDB 快照 |
| 阻塞情况 | 子进程执行,主线程仅在最后替换文件时短暂阻塞 | 子进程执行,主线程无阻塞(fork 瞬间微阻塞) |
| 数据完整性 | 补全重写期间的新操作,数据 100% 完整 | 仅记录快照时刻数据,丢失快照后到宕机前的操作 |
| 文件体积 | 去冗余后体积小,启动速度快 | 体积小,启动速度极快 |
注意:
混合持久化(Redis 4.0+ 新增)
开启 aof-use-rdb-preamble yes 后,AOF 重写的逻辑变了:
- 子进程先把当前内存数据生成一份 RDB 二进制快照 ,写到新 AOF 文件的开头;
- 再把重写期间(fork 之后)父进程
aof_rewrite_buf里的增量命令,以AOF 文本格式追加到 RDB 快照后面; - 最终生成一个「前半段 RDB 二进制 + 后半段 AOF 文本」的混合 AOF 文件。
所以可以是 AOF 的一个增强吧,AOF 重写之后可以不直接是文本格式了,可以和 RDB 是一样的二进制格式,这样读取更快,当然后面自动记录的依旧是文本格式!
启动时数据恢复
当 Redis 启动时,会根据 RDB 和 AOF 文件的内容,进行数据恢复,流程如下:

检查是否开启 AOF:
- 若开启,优先尝试加载 AOF 文件。
- 若 AOF 不存在,再尝试加载 RDB 文件。
- 若 AOF 关闭,直接尝试加载 RDB 文件。
加载成功则启动成功,加载失败则启动失败。
本章要点回顾
- Redis 提供了两种持久化方案:RDB 和AOF。
- RDB 视为内存的快照,持久化内容更的紧凑,占空间小,恢复时速度更快。但生成 RDB 的开销大,不适合实时持久化。
- AOF 视为对修改命令保存,在恢复时需要重放命令,并且有重写机制来定期压缩 AOF 文件。
- RDB 和 AOF 都使用
fork创建子进程,利用 Linux 子进程拥有父进程内存快照的特点进行持久化,尽可能不影响主进程继续处理后续命令。