fork 阻塞与写时复制内存放大

举报
yd_94907100 发表于 2026/11/09 08:11:14 2026/11/09
【摘要】 用 latest_fork_usec 与 cow size 量化快照期的停顿与内存

BGSAVE、AOF 重写、无盘复制都始于一次 fork。fork 带来两类成本:复制页表造成的主线程停顿,以及写时复制造成的内存放大。本文把两类成本量化,并给出内存规划与参数层面的应对。

一、fork 在 Redis 里的位置

fork 创建子进程后,子进程持有父进程内存的只读视图,父子共享同一批物理内存页;父进程修改某一页时,内核才复制该页,这就是写时复制(Copy-On-Write)。对 Redis 而言,fork 让"快照期间服务不中断"成为可能,代价分散在两个时刻:fork 瞬间的页表复制,以及之后整个快照期间的 COW 放大。理解这两段成本各自的时间尺度,才能解释"为什么快照一开延迟就抖"。

三个会 fork 的动作值得区分,因为它们的子进程生命周期差别很大:

| 动作 | 子进程干什么 | 持续时间 | COW 来源 |

| --- | --- | --- | --- |

| BGSAVE | 把全量内存写成 RDB 文件 | 与数据量、磁盘速度正相关 | 快照期间的写命令 |

| BGREWRITEAOF | 重写成最小命令集的 base 文件 | 同上,通常更长 | 重写期间的写命令 |

| 无盘复制 | 直接通过 socket 把 RDB 发给从库 | 与网络带宽相关 | 全量同步期间的写命令 |

三者共同点是"子进程要遍历全量内存",因此 fork 停顿和 COW 窗口都会出现;差别在于子进程存活时间——无盘复制受网络带宽拖累,慢链路下 COW 窗口可能比本地快照长得多,这是跨机房部署常被忽略的一点。

二、fork 停顿有多久

redis-cli INFO stats | grep -E "latest_fork_usec|total_forks"
# latest_fork_usec:12543
# total_forks:482

latest_fork_usec 是最近一次 fork 的耗时(微秒)。fork 需要复制整个进程的页表,耗时与内存大小近似线性:关闭透明大页时,1 GB 内存约 10~20 ms,40 GB 的实例就是数百毫秒级别的停顿。这段时间主线程不能处理命令,表现为周期性延迟毛刺,监控上能看到与快照节奏一致的 P99 尖峰。total_forks 累计 fork 次数,结合快照配置可以算出停顿发生的频率。

页表之所以要"复制"而不是"共享",是因为内核需要为子进程建立独立的虚拟地址到物理页的映射。Linux 用多级页表,64 位系统上 4 KB 页对应每 512 个页表项占一级,一个 40 GB 的进程在 4 KB 页下约有一千万个页,页表本身就要占用几十 MB。fork 时内核逐个复制这些表项并把父子两边的页都标记为只读,工作量与页数量成正比——这是停顿无法通过"只复制一次"消除的原因。如果 THP 开启,同样的内存用 2 MB 大页表示时页数减少 512 倍,页表复制更快,但 COW 的粒度变粗,两者是同一个权衡的两面。

三、COW 内存放大

快照期间父进程继续写入,每个被修改的 4 KB 内存页都要复制一份。最坏情况(所有页都被碰过)内存接近翻倍,真实负载下放大比例取决于快照时长内的写入率:

redis-cli INFO persistence | grep cow
# rdb_last_cow_size:536870912

rdb_last_cow_size 是上次快照期间复制的字节数。观察技巧:快照进行时对比 INFO memory 里的 used_memory_rss 与 used_memory,两者差值拉大说明 COW 正在发生;此时 mem_fragmentation_ratio 升高是正常现象,不要误判为碎片去开 activedefrag。

透明大页(THP)是最大的放大器:它把复制单位从 4 KB 变成 2 MB,父进程碰一个字节就复制 2 MB,放大 512 倍。生产必须关闭:

cat /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/enabled

四、内存规划

两条经验线:

  • 物理机:maxmemory 不超过物理内存的 50%~60%,剩余部分留给 COW、页表与页缓存。
  • 容器:cgroup memory limit 同样要留出 COW 余量,limit 贴着 used_memory 设置时,快照期间极易被 OOMKilled,表现为"一到整点快照就重启"。

fork 能否成功还依赖内核的内存超额分配策略,需要显式设置:

sysctl vm.overcommit_memory=1

设为 0 时,大内存实例的 fork 可能因为"内存不够"被内核直接拒绝,BGSAVE 失败;Redis 启动日志检测到这一项配置不对时也会专门打印警告。

五、降低影响的手段

  • 错峰快照:把 save 条件避开写入高峰,或用定时任务在凌晨触发;
  • 拆分大实例:40 GB 的单实例拆成 4 个 10 GB,fork 停顿同步降为四分之一;
  • 削峰写入:能预知的批量写入(缓存预热、数据订正)避开快照窗口,直接减少被复制的页数;
  • 缩短快照时间:更快的磁盘缩短子进程写盘时长,COW 窗口随之缩短。

六、一次延迟毛刺的排查过程

把上面的指标串起来,能形成一条可复用的排查路径。某实例 P99 从 3 ms 涨到 40 ms,告警日志没有慢命令:

# 1. 先看是否有 fork 发生
redis-cli INFO stats | grep -E "latest_fork_usec|total_forks"
# latest_fork_usec:186342
# total_forks:1247

# 2. 看快照与 COW
redis-cli INFO persistence | grep -E "rdb_bgsave_in_progress|rdb_last_cow_size"
# rdb_bgsave_in_progress:0
# rdb_last_cow_size:3221225472

# 3. 看内存
redis-cli INFO memory | grep -E "used_memory_human|used_memory_rss_human|mem_fragmentation_ratio"
# used_memory_human:18.42G
# used_memory_rss_human:24.87G
# mem_fragmentation_ratio:1.35

三个数字连起来读:latest_fork_usec 186 ms 意味着单次停顿就足以让 P99 突破 100 ms;rdb_last_cow_size 3 GB 说明快照期间被修改的页很多,18 GB 的实例放大了约 16%;total_forks 1247 说明这个动作已经发生了上千次,不是偶发。结论是快照节奏过密叠加内存偏大。

处理顺序:先把 save 条件放宽、把 auto-aof-rewrite-min-size 调大,减少 fork 频次;再把 maxmemory 下调或拆分实例,降低单次停顿;最后确认 THP 已关闭、vm.overcommit_memory 为 1。三项做完,latest_fork_usec 通常能压到 50 ms 以内。

提示:mem_fragmentation_ratio 1.35 在快照刚结束时偏高是正常的,要结合 rdb_bgsave_in_progress 与时间点判断。如果快照结束数小时后仍不回落,才值得怀疑真实碎片。

常见坑 / 最佳实践

  1. 不要用 used_memory 做容量规划。 以 used_memory_rss 与 COW 余量为准,maxmemory 与内存上限之间至少留 30%~40% 空间。
  2. 容器内的内核参数要在宿主机设。 overcommit_memory 是 sysctl,容器里改不了;THP 状态同样取决于宿主机,部署前逐节点检查。
  3. 监控 latest_fork_usec 趋势。 随内存增长同步上升,持续超过 100 ms 就要考虑拆分实例。
  4. 别把快照期的内存上涨当泄漏。 COW 是暂态的,快照结束回落;用 rdb_last_cow_size 与时间点对齐后再下结论。
  5. 高峰期避免手动 BGSAVE。 应急备份优先在从库执行,主库高峰期 fork 的停顿与 COW 都被放大。

小结

  • fork 的成本分两段:复制页表造成的主线程停顿,与快照期间的写时复制内存放大。
  • latest_fork_usec 与 rdb_last_cow_size 是量化这两个成本的现成指标,应纳入监控。
  • THP 把 COW 单位放大到 2 MB,生产环境必须设为 never。
  • 内存规划要给 COW 留余量:物理机 maxmemory 不超 60%,容器 limit 同样如此,overcommit_memory 设为 1。
  • 错峰、拆分、削峰写入、缩短快照窗口,是从源头减少 fork 影响的四类手段。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。