内存怎么落到磁盘
Redis 快,因为数据活在内存里;但内存一断电就没了。它靠两件武器把数据刻到磁盘——RDB 快照和 AOF 日志。而让"边存盘边服务"成为可能的,是一个精巧的机制:fork + 写时复制。
快照 与 日志
数据在内存里易失,一断电就清零。Redis 把它刻到磁盘的方式有两种,思路完全不同:
给整个内存拍照片
- 某一时刻的全量二进制快照
- 文件紧凑、恢复快
- 风险:丢失"上次快照之后"的写
记下每一条写命令
- 把每个写操作追加进日志
- 更耐用(最多丢约 1 秒)
- 代价:文件更大、恢复更慢
RDB 像定期给房间拍全景照:一张照片还原全貌,但两张照片之间的变化没记下。AOF 像记流水账:每挪动一件东西都写一笔,事后照账本一步步复原。
给内存拍快照
RDB 是某一时刻内存的全量二进制镜像(dump.rdb)。它由几种方式触发:
此外,主从全量同步(主库生成 RDB 发给副本)和正常关机也会触发。RDB 的优点是文件小、重启加载快;缺点是两次快照之间的写入,一旦崩溃就丢了。
非阻塞快照的秘密:fork 与写时复制
问题来了:给几十 GB 的内存"拍照"要花时间,难道要停下服务干等?不用——这正是 BGSAVE 里 fork + 写时复制(COW) 的精妙之处。
fork 出一个子进程,它和父进程共享同一批内存页(并不真的复制一份数据)。子进程按这个"一致的瞬间视图"把快照慢慢写盘,父进程则继续处理读写。当父进程要改某一页时,操作系统才把那一页复制一份给它改,子进程手里仍是旧值——快照因此保持一致。
所以额外内存开销不等于整个数据集,而是正比于存盘期间被改动的页量。写得越猛,复制的页越多。
两个现实代价:① fork() 本身会有一次延迟尖峰,且正比于数据集大小(页表越大越明显)——超大实例尤其要留意。② 建议预留 30–50% 的内存余量,避免存盘时因 COW 复制页而触发交换(swap);关闭透明大页(THP),否则每次复制的页更大、抖动更狠。
写时复制 = "先都共用,谁改了才给谁单独复印一份"。这让 Redis 能边拍照边营业。
记录每一条写命令
AOF 把每个写命令按 RESP 协议追加到日志里,重启时重放一遍就能还原数据。它的耐用度由 fsync 策略决定:
| fsync 策略 | 含义 | 取舍 |
|---|---|---|
| always | 每个写都刷盘 | 最安全,最慢 |
| everysec | 每秒批量刷盘 | 最多丢约 1 秒,推荐 |
| no | 交给操作系统决定 | 最快,最不安全 |
日志会越写越大(同一个键反复写会留下一堆冗余命令),于是有了重写(rewrite):BGREWRITEAOF(或按 auto-aof-rewrite-percentage 100 / auto-aof-rewrite-min-size 64mb 自动触发)会 fork 子进程,用最少的命令重新生成一份紧凑日志。
升级到 7.0 的一个坑:AOF 不再是单个 appendonly.aof,而是 appendonlydir/ 目录下的多部分文件(base + incr + manifest)。旧的"只备份一个 aof 文件"的脚本会失效——备份 / 恢复要针对整个目录和 manifest。
快照打底,日志收尾
RDB 恢复快但会丢数据,AOF 耐用但加载慢——能不能两者兼得?能,这就是混合持久化(aof-use-rdb-preamble yes,现代版本默认开启)。
AOF 重写时,生成的基底文件用 RDB 格式(加载快),后面再接增量的 AOF 命令(保证耐用)。重启时先秒读 RDB 基底、再重放少量增量——又快又几乎不丢。
生产上被广泛采用的组合就是:混合持久化 + appendfsync everysec——近乎零丢失、重启不算慢、磁盘占用可控。
四种组合,按需取舍
| 方案 | 适合 | 代价 |
|---|---|---|
| 只用 RDB | 能容忍丢几分钟数据,要快照备份 / 快速重启 | 崩溃丢"上次快照后"的写;周期性 fork 开销 |
| 只用 AOF | 要尽量少丢数据 | 文件更大、加载更慢(无 RDB 基底时) |
| 混合(推荐) | 绝大多数生产场景 | 基本没有短板,配置略多 |
| 都不开 | 纯缓存,丢了无所谓 | 重启即清空 |
同时开启 RDB 与 AOF 时,重启优先加载 AOF(更完整、更新)。
边拍照,边营业
Redis 用 RDB 快照换恢复速度、用 AOF 日志换耐用度,再用 fork + 写时复制让"存盘"不打断"服务"。现代默认的混合持久化把两者的长处合在一起——生产上配上 everysec,就是又快又稳的落盘方案。


