引言
Redis 作为最流行的内存数据库之一,其持久化机制一直是面试中的高频考点。AOF(Append Only File)通过记录写命令来实现持久化,但随着时间推移,AOF 文件会不断膨胀。为了解决这个问题,Redis 引入了 AOF 重写(Rewrite) 机制。而重写过程中最核心的技术,就是 fork 与写时复制(Copy-On-Write, COW)。
本文将深入剖析 AOF 重写的完整流程,并详细讲解 fork 写时复制在其中的作用,帮助你从容应对面试官的追问。
一、为什么需要 AOF 重写
AOF 持久化会记录每一个写命令。例如:
SET name "redis"
SET name "mysql"
SET name "mongodb"
这三条命令最终的效果等价于 SET name "mongodb",但 AOF 文件却记录了三条。随着运行时间增长,AOF 文件会变得非常大,带来两个问题:
- 文件体积膨胀:占用大量磁盘空间。
- 恢复速度变慢:Redis 重启时需要逐条重放命令,文件越大恢复越慢。
AOF 重写的目的,就是用最少的命令来等价地描述当前数据集的状态,从而生成一个精简的新 AOF 文件。
二、AOF 重写的触发方式
AOF 重写有两种触发方式:
1. 手动触发
执行 BGREWRITEAOF 命令,手动触发一次后台重写。
2. 自动触发
通过配置项控制:
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
auto-aof-rewrite-percentage:当前 AOF 文件大小相比上次重写后的大小增长了多少百分比时触发。auto-aof-rewrite-min-size:AOF 文件达到的最小体积才允许触发。
两个条件同时满足时,才会自动触发重写。
三、AOF 重写的核心流程
AOF 重写由 子进程 完成,主进程继续处理客户端请求。整体流程如下:
步骤 1:fork 出子进程
主进程调用 fork() 系统调用,创建一个子进程。子进程拥有与主进程完全相同的内存数据副本(逻辑上)。
步骤 2:子进程遍历内存生成新 AOF
子进程根据当前内存中的数据集,遍历所有键值对,将每个键值对转换成一条或几条写命令,写入新的 AOF 临时文件。
例如,对于一个包含 100 个元素的 List,子进程会生成一条 RPUSH 命令,而不是 100 条 RPUSH,从而大幅精简文件。
步骤 3:主进程同时处理新写入
在子进程重写期间,主进程并没有停止服务,仍然在接收客户端的写命令。这些新命令会:
- 追加到现有的 AOF 缓冲区(
aof_buf),保证原有 AOF 文件不丢失数据。 - 同时追加到AOF 重写缓冲区(
aof_rewrite_buf)。
这个重写缓冲区非常关键,它记录了重写期间产生的增量数据。
步骤 4:子进程完成重写,通知主进程
子进程写完新 AOF 文件后,向主进程发送信号。主进程收到后:
- 将
aof_rewrite_buf中的增量命令追加到新 AOF 文件中。 - 用新 AOF 文件原子替换旧 AOF 文件。
至此,重写完成。
流程图概览
主进程 fork 出子进程
│
├──► 子进程:遍历内存 → 写入新 AOF 临时文件
│
└──► 主进程:继续处理请求
├─ 写入 aof_buf(旧 AOF)
└─ 写入 aof_rewrite_buf(增量)
子进程完成 → 通知主进程
│
▼
主进程:将 aof_rewrite_buf 追加到新 AOF
│
▼
原子替换旧 AOF 文件
四、fork 与写时复制(Copy-On-Write)
1. fork 的基本原理
Linux 中的 fork() 会创建一个子进程,传统实现会完整复制父进程的内存空间,效率极低。而现代操作系统采用 写时复制(COW) 技术来优化。
2. 什么是写时复制
fork 之后,父子进程共享同一份物理内存页,页表指向相同的物理地址。只有当某一方尝试修改某个内存页时,内核才会为该页复制一份副本,供修改方使用。
这样做的好处:
- fork 时几乎不复制内存,速度极快。
- 只有在真正发生写操作时才复制,节省内存。
3. COW 在 AOF 重写中的应用
- 子进程 fork 出来后,与主进程共享内存数据,因此子进程看到的是 fork 那一刻的数据快照。
- 主进程在重写期间继续处理写请求,一旦修改某个内存页,就会触发 COW,为该页复制副本。主进程在副本上修改,子进程仍看到旧数据。
- 因此,子进程生成的新 AOF 文件,等价于 fork 瞬间的数据状态。
4. COW 带来的问题
COW 虽然高效,但并非没有代价:
- 内存膨胀:如果重写期间写入非常频繁,会触发大量页复制,导致内存占用翻倍甚至更多。
- 性能抖动:页复制本身有开销,写入压力大时可能影响主进程性能。
因此,合理控制重写频率、避免在高峰期触发重写,是运维的重要考量。
五、面试常见追问
追问 1:重写期间的新写命令会丢失吗?
不会。重写期间的新命令会同时写入 aof_buf(旧 AOF)和 aof_rewrite_buf(增量缓冲区)。重写完成后,增量缓冲区的内容会追加到新 AOF 中,保证数据完整。
追问 2:为什么用子进程而不是子线程?
- 子进程拥有独立的内存空间,即使发生 COW,也不会影响主进程的稳定性。
- 子进程崩溃不会导致主进程崩溃。
- Redis 本身是单线程模型,使用子进程可以避免线程安全问题。
追问 3:AOF 重写期间,Redis 会阻塞吗?
fork 调用本身会短暂阻塞主进程(通常非常短,取决于内存页表大小)。fork 之后,主进程继续处理请求,基本不阻塞。只有在最后替换 AOF 文件时会有极短暂的阻塞。
追问 4:COW 一定会发生吗?
只有主进程在重写期间修改了某个内存页,才会触发该页的 COW。如果重写期间没有写操作,则不会发生任何页复制。
六、总结
| 关键点 | 说明 |
|---|---|
| 重写目的 | 精简 AOF 文件,加快恢复速度 |
| 执行者 | fork 出的子进程 |
| 核心机制 | fork + 写时复制(COW) |
| 数据一致性 | aof_rewrite_buf 记录增量,最终合并 |
| 优点 | 不阻塞主进程,内存开销小 |
| 风险 | 高频写入时内存膨胀、性能抖动 |
理解 AOF 重写和 fork 写时复制,不仅能帮助你在面试中脱颖而出,更能让你在实际运维中合理配置 Redis,避免踩坑。建议结合源码(aof.c 中的 rewriteAppendOnlyFileBackground)进一步深入学习。
未经允许不得转载:任鹏个人博客 » Redis 的 AOF 重写流程和 fork 写时复制

