Redis 的 AOF 重写流程和 fork 写时复制

引言

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 文件会变得非常大,带来两个问题:

  1. 文件体积膨胀:占用大量磁盘空间。
  2. 恢复速度变慢: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:主进程同时处理新写入

在子进程重写期间,主进程并没有停止服务,仍然在接收客户端的写命令。这些新命令会:

  1. 追加到现有的 AOF 缓冲区aof_buf),保证原有 AOF 文件不丢失数据。
  2. 同时追加到AOF 重写缓冲区aof_rewrite_buf)。

这个重写缓冲区非常关键,它记录了重写期间产生的增量数据

步骤 4:子进程完成重写,通知主进程

子进程写完新 AOF 文件后,向主进程发送信号。主进程收到后:

  1. aof_rewrite_buf 中的增量命令追加到新 AOF 文件中。
  2. 用新 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 写时复制

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏