【Redis】AOF 重写机制:为什么重写、后台重写与写时复制
一句话概括:AOF 重写 = 子进程基于当前内存快照生成新 AOF + 父进程继续处理请求 + COW 保证快照稳定 + rewrite buffer 保证新写入不丢失。
一、为什么需要 AOF 重写
Redis 的 AOF 持久化,本质是把每一条写命令追加到 AOF 文件里。比如客户端依次执行:
SET name zhangsan
SET name lisi
SET name wangwuAOF 文件会把这三条命令都记录下来。但实际上 Redis 当前最终状态只有一个:
name = wangwu前面两条命令已经没有恢复价值了。Redis 如果重启,仍然需要按顺序重新执行这些历史命令——AOF 文件越大,恢复速度就越慢,磁盘占用也越高。
所以 Redis 需要 AOF 重写。
二、AOF 重写做了什么
AOF 重写不是在原来的 AOF 文件里删除无效命令,也不是简单压缩旧文件,而是根据 Redis 当前内存中的最终数据状态,重新生成一份全新的 AOF 文件。
比如原 AOF 里有:
SET name zhangsan
SET name lisi
SET name wangwu
INCR age
INCR age
INCR age当前 Redis 内存中的结果是:
name = wangwu
age = 3那么重写后的 AOF 变成:
SET name wangwu
SET age 3新 AOF 文件只保留恢复当前数据所必需的最少命令集,体积大幅缩小,重启恢复也更快。
AOF 重写通过 BGREWRITEAOF 触发。注意区分:BGREWRITEAOF 是 AOF 重写,BGSAVE 是 RDB 快照保存——不过它们底层思想很像:都通过 fork 创建子进程在后台做持久化,主进程继续处理客户端请求。
三、为什么要用后台子进程
重写需要遍历当前数据库中的全部数据。如果放在 Redis 主线程里执行,Redis 会长时间阻塞,客户端请求无法正常处理。
Redis 主进程
|
| fork
v
子进程:根据当前内存数据生成新的 AOF 文件
父进程:继续处理客户端请求Redis 通过 fork 出一个子进程,由子进程根据 fork 那一刻的内存状态生成新 AOF,父进程继续负责读写请求。
四、Copy On Write(写时复制)
4.1 fork 不会立刻复制全部内存
fork 的时候,Redis 内存里可能有几十 GB 数据。如果直接复制一整份出来,不仅慢而且内存直接翻倍——这在生产环境不可接受。
Linux 的 fork 使用的是 Copy On Write(写时复制)。fork 之后,父进程和子进程共享同一份物理内存,并不立刻复制。只有当父进程要修改某个内存页时,操作系统才会把这一页复制出来。
4.2 COW 的工作流程
比如 fork 后有一个内存页 page A:
page A:
user:1 = 张三
user:2 = 李四fork 后父子进程共享它:
父进程 ----\
page A
子进程 ----/此时父进程执行:
SET user:1 王五父进程要修改 page A,但子进程还要基于 fork 那一刻的数据生成 AOF。如果直接修改,子进程看到的数据就被污染了。所以操作系统会复制一份 page A:
旧 page A(子进程继续使用)
user:1 = 张三
user:2 = 李四
新 page A(父进程修改)
user:1 = 王五
user:2 = 李四4.3 COW 的常见误解
❌ 错误认知:每次写操作都复制一份完整 Redis 数据,或者一个 page 被写多少次就复制多少次。
✅ 实际情况:一个内存页在一次 fork 生命周期中,通常最多只会被复制一次。
比如 page A 第一次被修改时发生复制,之后父进程已经拥有了自己的 page A 副本。如果再次修改同一个 page:
SET user:2 赵六父进程直接修改自己的 page A 就行,不会再触发复制。
所以不是:
写一次,复制一份
写两次,复制两份
写一百次,复制一百份而是:
某个 page 第一次被写时 → 复制一次
之后这个 page 再被父进程写 → 直接修改自己的副本4.4 大量写入的风险
但这不意味着 AOF 重写期间大量写入没有风险。
如果重写期间写入非常多,修改到了大量不同的内存页,那么这些页每个都会触发一次 COW,Redis 的额外内存占用就会上升。
例如:Redis 原本占用 100GB 内存,AOF 重写期间有大量更新,修改了约 40GB 的数据页,那么 COW 可能额外产生接近 40GB 的内存占用。
此时机器实际内存压力明显增加,严重时可能出现 swap、性能骤降,甚至 OOM。
面试常见问题:AOF 重写期间大量写入会有什么问题?
答案:大量写入会导致大量内存页触发写时复制,从而增加额外内存占用,可能造成内存压力甚至 OOM。
五、AOF 重写缓冲区:新写入如何不丢失
子进程是根据 fork 那一刻的内存快照生成新 AOF 的。但 fork 之后客户端又写入的新数据怎么办?
比如子进程正在生成新 AOF,父进程又执行了:
SET city beijing子进程的快照里没有这条新数据。如果不处理,新 AOF 文件就会丢失重写期间的新写入。
Redis 的解决方式是 AOF 重写缓冲区(AOF rewrite buffer)。
在 AOF 重写期间,父进程收到新的写命令,会同时做两件事:
1. 继续追加到旧 AOF 文件 → 保证旧 AOF 仍然完整
2. 写入 AOF rewrite buffer → 记录重写期间的新命令当子进程生成新 AOF 文件完成后,父进程会把 AOF rewrite buffer 中的命令追加到新 AOF 文件末尾。这样新 AOF 文件就包含两部分数据:
┌─────────────────────────────────────┐
│ 1. fork 那一刻内存中的最终状态 │
│ 2. fork 之后重写期间新增的写命令 │
└─────────────────────────────────────┘最后 Redis 用新 AOF 文件原子替换旧 AOF 文件,整个重写过程完成。
六、总结
| 机制 | 作用 |
|---|---|
BGREWRITEAOF | fork 子进程后台重写,主进程不阻塞 |
| Copy On Write | 避免 fork 时复制全部内存,按需复制被修改的页 |
| AOF rewrite buffer | 记录重写期间的新写入,追加到新 AOF 末尾 |
| 原子替换 | 新 AOF 完成后替换旧 AOF |
AOF 重写的核心逻辑:Redis 不直接修改旧 AOF,而是根据当前内存状态重新生成一份更小的新 AOF。整个过程中子进程负责生成、父进程继续服务请求、COW 控制内存成本、rewrite buffer 保证数据完整——四个机制协同,在不中断服务的前提下完成 AOF 文件的瘦身。