很多同学在面试中被问到 Redis 持久化时,都能答出 RDB 和 AOF 的区别,但一旦面试官追问:“RDB 在 fork 子进程时,为什么会导致内存膨胀?” 不少人就开始含糊了。有人说是因为写时复制(Copy-On-Write)本身就会复制内存,也有人说是 fork 本身会拷贝页表。这些说法都不够准确。今天我们就从 Linux fork 的底层机制出发,把这个问题彻底讲清楚。
一、先明确一个前提:fork 并不复制物理内存
在 Linux 中,fork() 创建子进程时,并不会立刻把父进程的物理内存全部复制一份。相反,内核采用了 写时复制(Copy-On-Write,COW) 机制:
- 父进程和子进程最初共享同一份物理内存页。
- 内核只复制页表(Page Table),让子进程的虚拟地址指向相同的物理页。
- 这些共享的物理页被标记为只读。
- 当任意一方尝试写入某个页时,CPU 触发缺页异常,内核才为该页分配新的物理内存,并复制原页内容。
所以,fork 的瞬间并不会导致物理内存翻倍,真正被复制的只是页表。那为什么 Redis 做 RDB 时,内存会明显膨胀呢?关键在于 Redis 在 fork 之后仍然在持续处理写请求。
二、内存膨胀的根源:COW 页复制
RDB 持久化的流程是:
- 主进程 fork 出一个子进程。
- 子进程负责把当前内存中的数据快照写入磁盘。
- 主进程继续处理客户端请求,不阻塞。
问题就出在第 3 步。子进程在遍历内存写 RDB 文件的过程中,主进程可能正在执行写命令(SET、DEL、LPUSH 等)。一旦主进程写入某个内存页,COW 机制就会触发:
- 内核为主进程分配一个新的物理页。
- 把旧页的内容复制到新页。
- 主进程在新页上修改。
- 子进程仍然读取旧页,从而保证快照的一致性。
每发生一次这样的写操作,就会多出一份物理内存。 如果 fork 期间写入非常频繁,被复制的页就会越来越多,Redis 占用的物理内存就会不断攀升。这就是所谓“内存膨胀”的本质。
三、内存膨胀到底能有多大?
理论上,最坏情况下,主进程在 fork 期间修改了所有内存页,那么内存占用会接近翻倍。当然实际中很少这么极端,但以下几种情况会显著放大膨胀:
- 写入量巨大:比如大量 SET、过期删除、LRU 淘汰,都会触发页复制。
- 内存页数量多:Redis 实例内存越大,页表越大,COW 复制的潜在页数也越多。
- 透明大页(THP)开启:THP 会把多个 4KB 页合并成 2MB 大页。一旦触发 COW,复制的是整个 2MB,而不是 4KB,内存膨胀会被成倍放大。这也是 Redis 官方建议关闭 THP 的重要原因。
- fork 耗时过长:内存越大,页表复制越慢,fork 阻塞主进程的时间也越长,期间积压的写请求在 fork 完成后集中执行,进一步加剧 COW。
四、面试常见追问
追问 1:fork 时复制页表本身会占内存吗?
会,但通常很小。页表大小与虚拟内存规模相关,一般每个页表项几字节,相对于数据本身可以忽略。真正的大头是 COW 导致的物理页复制。
追问 2:为什么子进程不直接读主进程内存,而非要 COW?
因为要保证快照一致性。如果子进程直接读主进程正在修改的内存,写出的 RDB 文件可能包含“半新半旧”的数据,恢复时数据就乱了。COW 让子进程始终看到 fork 那一刻的内存视图。
追问 3:如何缓解 RDB fork 的内存膨胀?
- 关闭透明大页(THP)。
- 控制单实例内存,避免过大(建议 10GB 以内,视业务而定)。
- 合理配置
save策略,避开写入高峰。 - 使用主从架构,在从节点做 RDB,减轻主节点压力。
- 考虑混合持久化或 AOF,减少全量 fork 的频率。
追问 4:COW 期间内存膨胀会导致 OOM 吗?
会。如果系统可用内存不足,COW 复制新页时可能触发 OOM Killer,把 Redis 进程杀掉。所以生产环境要预留足够内存,通常建议预留 50% 以上。
五、总结
一句话概括:RDB fork 本身不复制物理内存,但 fork 之后主进程的写操作会触发 COW,导致被修改的内存页被复制,从而产生内存膨胀。 膨胀程度取决于 fork 期间的写入量和内存页大小,最坏情况下接近翻倍。理解这一点,不仅能答好面试题,也能帮助你在生产环境中正确规划 Redis 的内存和持久化策略。
未经允许不得转载:任鹏个人博客 » Redis 的 RDB fork 时为什么会产生内存膨胀

